View Javadoc

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