View Javadoc

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 }