1 /* 2 * Copyright 2012 smartics, Kronseder & Reiner GmbH 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package de.smartics.maven.issue.command; 17 18 import java.io.IOException; 19 import java.io.InputStream; 20 import java.io.Serializable; 21 import java.util.List; 22 23 import org.apache.commons.httpclient.Header; 24 import org.apache.commons.httpclient.HttpMethod; 25 import org.apache.commons.httpclient.methods.PostMethod; 26 import org.apache.commons.io.IOUtils; 27 28 import de.smartics.maven.issue.command.CommandResult.HttpStatus; 29 import de.smartics.maven.issue.command.CommandResult.Page; 30 import de.smartics.maven.issue.util.Grabber; 31 32 /** 33 * Base implementation of the {@link Command} interface. 34 * 35 * @param <T> the concrete type of the command. 36 */ 37 public abstract class AbstractCommand<T extends Command<T>> implements 38 Command<T> 39 { 40 // ********************************* Fields ********************************* 41 42 // --- constants ------------------------------------------------------------ 43 44 /** 45 * The class version identifier. 46 * <p> 47 * The value of this constant is {@value}. 48 * </p> 49 */ 50 private static final long serialVersionUID = 1L; 51 52 // --- members -------------------------------------------------------------- 53 54 /** 55 * The name of the service on the target server. 56 * 57 * @serial 58 */ 59 protected final String service; 60 61 /** 62 * The arguments to the command. 63 * 64 * @serial 65 */ 66 protected final List<CommandArgument<T>> arguments; 67 68 /** 69 * The result of the command execution. May be <code>null</code> if the 70 * command has not been executed yet or if the execution of the command failed 71 * without a result. 72 * 73 * @serial 74 */ 75 protected CommandResult<T> result; 76 77 /** 78 * The URL to the service that has been called. The value is <code>null</code> 79 * if the command was not yet executed. 80 * 81 * @serial 82 */ 83 protected String url; 84 85 // ****************************** Initializer ******************************* 86 87 // ****************************** Constructors ****************************** 88 89 /** 90 * Default constructor. 91 * 92 * @param service the name of the service on the target server. 93 * @param arguments the arguments to the command. 94 */ 95 protected AbstractCommand(final String service, 96 final List<CommandArgument<T>> arguments) 97 { 98 this.service = service; 99 this.arguments = arguments; 100 } 101 102 // ****************************** Inner Classes ***************************** 103 104 /** 105 * Contains information about the expectations and how they have been met. 106 */ 107 public static final class Expectation implements Serializable 108 { 109 // ******************************** Fields ******************************** 110 111 // --- constants ---------------------------------------------------------- 112 113 /** 114 * The class version identifier. 115 * <p> 116 * The value of this constant is {@value}. 117 * </p> 118 */ 119 private static final long serialVersionUID = 1L; 120 121 /** 122 * Constant for commands that do not check expectations. 123 */ 124 public static final Expectation NO_EXPECTATION = new Expectation(true, 125 null, null); 126 127 /** 128 * Signals that the result is not known to the command. Therefore it is a 129 * kind of unexpected result. 130 * <p> 131 * The value of this constant is {@value}. 132 * </p> 133 */ 134 public static final String UNKNOWN_FLAG = "UNEXPECTED"; 135 136 // --- members ------------------------------------------------------------ 137 138 /** 139 * The flag informs about the fact that the expectations have been met ( 140 * <code>true</code>) or not (<code>false</code>). 141 * 142 * @serial 143 */ 144 private final boolean expected; 145 146 /** 147 * The expected page title. 148 * 149 * @serial 150 */ 151 private final String expectedPageTitle; 152 153 /** 154 * The expectation flag that signals the effective result that has been 155 * evaluated on the check if the result is as expected. If there is more 156 * than one expected result (e.g. to add a component may result in the 157 * component being added, recognizing that the component was already added 158 * or in some kind of failure) the result of the analysis is stored here for 159 * further reference by the command itself. 160 * 161 * @serial. 162 */ 163 private final String flag; 164 165 // ***************************** Initializer ****************************** 166 167 // ***************************** Constructors ***************************** 168 169 /** 170 * Default constructor. 171 * 172 * @param expected the flag informs about the fact that the expectations 173 * have been met ( <code>true</code>) or not (<code>false</code>). 174 * @param expectedPageTitle the expected page title. 175 * @param flag the expectation flag that signals the effective result that 176 * has been evaluated on the check if the result is as expected. 177 */ 178 public Expectation(final boolean expected, final String expectedPageTitle, 179 final String flag) 180 { 181 this.expected = expected; 182 this.expectedPageTitle = expectedPageTitle; 183 this.flag = flag; 184 } 185 186 // ***************************** Inner Classes **************************** 187 188 // ******************************** Methods ******************************* 189 190 // --- init --------------------------------------------------------------- 191 192 // --- get&set ------------------------------------------------------------ 193 194 /** 195 * Returns the flag informs about the fact that the expectations have been 196 * met ( <code>true</code>) or not (<code>false</code>). 197 * 198 * @return the flag informs about the fact that the expectations have been 199 * met ( <code>true</code>) or not (<code>false</code>). 200 */ 201 public boolean isExpected() 202 { 203 return expected; 204 } 205 206 /** 207 * Returns the expected page title. 208 * 209 * @return the expected page title. 210 */ 211 public String getExpectedPageTitle() 212 { 213 return expectedPageTitle; 214 } 215 216 /** 217 * Returns the expectation flag that signals the effective result that has 218 * been evaluated on the check if the result is as expected. If there is 219 * more than one expected result (e.g. to add a component may result in the 220 * component being added, recognizing that the component was already added 221 * or in some kind of failure) the result of the analysis is stored here for 222 * further reference by the command itself. 223 * 224 * @return the expectation flag that signals the effective result that has 225 * been evaluated on the check if the result is as expected. 226 */ 227 public String getFlag() 228 { 229 return flag; 230 } 231 232 // --- business ----------------------------------------------------------- 233 234 // --- object basics ------------------------------------------------------ 235 } 236 237 // ********************************* Methods ******************************** 238 239 // --- init ----------------------------------------------------------------- 240 241 /** 242 * Helper to construct an URL to call a service on the target. 243 * 244 * @return the created URL. 245 */ 246 private String createUrl(final CommandTarget target) 247 { 248 final String targetUrl = target.getUrl(); 249 final StringBuilder buffer = new StringBuilder(128); 250 buffer.append(targetUrl).append('/').append(service); 251 return buffer.toString(); 252 } 253 254 // --- get&set -------------------------------------------------------------- 255 256 /** 257 * Returns the result of the command execution. May be <code>null</code> if 258 * the command has not been executed yet or if the execution of the command 259 * failed without a result. 260 * 261 * @return the result of the command execution. 262 */ 263 @Override 264 public final CommandResult<T> getResult() 265 { 266 return result; 267 } 268 269 // --- business ------------------------------------------------------------- 270 271 /** 272 * Creates the method to be executed with a HTTP client. 273 * 274 * @return the method to be executed with a HTTP client. 275 */ 276 private HttpMethod createMethod() 277 { 278 final PostMethod method = new PostMethod(url); 279 280 for (final CommandArgument<?> argument : arguments) 281 { 282 // FIXME: Ugly API argument.getName().getName() 283 final String name = argument.getName().getName(); 284 final String value = argument.getValue(); 285 if (value != null) 286 { 287 method.addParameter(name, value); 288 } 289 } 290 291 return method; 292 } 293 294 /** 295 * {@inheritDoc} 296 * 297 * @see de.smartics.maven.issue.command.AddVersionCommand#execute() 298 */ 299 @Override 300 public final CommandResult<T> execute(final CommandTarget target) 301 throws CommandException 302 { 303 if (url != null) 304 { 305 throw new CommandException("Command has already been executed: " + url); 306 } 307 308 url = createUrl(target); 309 final HttpMethod method = createMethod(); 310 final int code = target.execute(method); 311 312 final String pageContent = readPageContent(method); 313 314 final HttpStatus httpStatus = createStatus(method); 315 316 result = createResult(code, pageContent, httpStatus); 317 return result; 318 } 319 320 /** 321 * Returns the command argument with the specified name. 322 * 323 * @param parameter the paramter that defines the name of the requested 324 * argument. 325 * @return the requested argument. 326 */ 327 protected final CommandArgument<T> getArgument( 328 final CommandParameter<T> parameter) 329 { 330 for (final CommandArgument<T> argument : arguments) 331 { 332 if (parameter.equals(argument.getName())) 333 { 334 return argument; 335 } 336 } 337 338 throw new IllegalArgumentException("Command '" + this.url 339 + "' does not know parameter '" 340 + parameter + "'."); 341 } 342 343 // --- object basics -------------------------------------------------------- 344 345 private HttpStatus createStatus(final HttpMethod method) 346 { 347 final int code = method.getStatusCode(); 348 final String text = method.getStatusText(); 349 final HttpStatus httpStatus = new HttpStatus(code, text); 350 return httpStatus; 351 } 352 353 // CHECKSTYLE:OFF 354 /** 355 * Override if token should be read. 356 * 357 * @return <code>true</code> if token is required to be read from response, 358 * <code>false</code> (the default) otherwise. 359 */ 360 protected boolean isTokenRequiredToRead() // CHECKSTYLE:ON 361 { 362 return false; 363 } 364 365 private CommandResult<T> createResult(final int code, 366 final String pageContent, final HttpStatus httpStatus) 367 { 368 final Grabber grabber = new Grabber(pageContent); 369 final String title = grabber.grabTitle(); 370 final String token = grabber.grabToken(); 371 372 final CommandResult.Page page = 373 new CommandResult.Page(token, title, pageContent); 374 final Expectation expectation = checkExpectation(page); 375 final String description = getCommandResultDescription(page, expectation); 376 return new CommandResult<T>(code, httpStatus, description, page, 377 expectation); 378 } 379 380 /** 381 * Returns the description to the result. This description explains what 382 * happened in a short sentence. This is the essence of the command's result. 383 * The description of a command result may be <code>null</code>, if the 384 * command has no substantial result. A non-substantial result, which 385 * justifies a value of <code>null</code>, would be a navigation command to a 386 * certain page. 387 * 388 * @param page the resulting page to be analyzed to create the correct 389 * description. 390 * @param expecation information about the analyzed result. 391 * @return the description to the result. 392 */ 393 protected abstract String getCommandResultDescription(final Page page, 394 final Expectation expecation); 395 396 /** 397 * Analyzes the returned page and returns the expectation report. 398 * 399 * @param page the returned page to check. 400 * @return the report on the check. 401 */ 402 protected abstract Expectation checkExpectation(final Page page); 403 404 private String readPageContent(final HttpMethod method) 405 { 406 InputStream input = null; 407 try 408 { 409 input = method.getResponseBodyAsStream(); 410 final String encoding = getEncoding(method); 411 final String page = IOUtils.toString(input, encoding); 412 return page; 413 } 414 catch (final IOException e) 415 { 416 throw new CommandException("Problems reading page.", e); 417 } 418 finally 419 { 420 IOUtils.closeQuietly(input); 421 } 422 } 423 424 private String getEncoding(final HttpMethod method) 425 { 426 final Header header = method.getResponseHeader("Content-Type"); 427 if (header != null) 428 { 429 final String pattern = "; charset="; 430 final String value = header.getValue(); 431 final int delimiterIndex = value.lastIndexOf(pattern); 432 if (delimiterIndex != -1) 433 { 434 final int start = delimiterIndex + pattern.length(); 435 if (start < value.length() - 1) // NOPMD 436 { 437 final String encoding = value.substring(start).trim(); 438 return encoding; 439 } 440 } 441 } 442 return null; 443 } 444 445 // CHECKSTYLE:OFF 446 /** 447 * {@inheritDoc} 448 * 449 * @see java.lang.Object#toString() 450 */ 451 @Override 452 public String toString() // CHECKSTYLE:ON 453 { 454 final StringBuilder buffer = new StringBuilder(128); 455 456 if (url != null) 457 { 458 buffer.append(url); 459 } 460 461 for (final CommandArgument<?> argument : arguments) 462 { 463 buffer.append("\n ").append(argument); 464 } 465 466 return buffer.toString(); 467 } 468 }