View Javadoc

1   /*
2    * Copyright 2008-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.issues;
17  
18  import java.io.File;
19  import java.util.List;
20  import java.util.Locale;
21  
22  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
23  import org.apache.maven.plugin.logging.Log;
24  import org.apache.maven.reporting.MavenReportException;
25  import org.apache.maven.reporting.MavenReportRenderer;
26  import org.codehaus.plexus.util.StringUtils;
27  import org.eclipse.mylyn.tasks.core.data.TaskData;
28  
29  import de.smartics.maven.issues.cache.TaskDataCache;
30  
31  /**
32   * The issues Mojo reports on issues.
33   *
34   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
35   * @version $Revision:591 $
36   */
37  public abstract class AbstractIssuesConnectionMojo extends AbstractReportMojo
38  {
39    // ********************************* Fields *********************************
40  
41    // --- constants ------------------------------------------------------------
42  
43    // --- members --------------------------------------------------------------
44  
45    /**
46     * In offline mode the plugin will not generate a report.
47     *
48     * @parameter default-value="${settings.offline}"
49     * @required
50     * @readonly
51     * @since 1.0
52     */
53    protected boolean offline;
54  
55    /**
56     * A simple flag to skip the generation of the reports. If set on the command
57     * line use <code>-Dissues.skip</code>.
58     *
59     * @parameter expression="${issues.skip}" default-value="false"
60     * @since 1.0
61     */
62    protected boolean skip;
63  
64    /**
65     * List of report output names to be skipped. This is useful to remove the
66     * generation of a particular report without removing the description. This
67     * configuration can also be used to remove configured reports in a parent's
68     * POM.
69     *
70     * @parameter
71     * @since 1.0
72     */
73    private List<String> skipReports;
74  
75    // ... version ..............................................................
76  
77    /**
78     * The name of the class that creates comparable version instances. The class
79     * specified here has to implement
80     * {@link de.smartics.maven.issues.version.VersionFactory}.
81     *
82     * @required
83     * @parameter
84     *            default-value="de.smartics.maven.issues.bugzilla.BugzillaVersionFactory"
85     * @since 1.0
86     */
87    private String versionFactory;
88  
89    /**
90     * The range of version to be included in the search. Use <code>[]</code> to
91     * specify inclusion and <code>()</code> for exclusion of the bounds.
92     * <p>
93     * Examples:
94     * </p>
95     * <ul>
96     * <li><code>1.0</code> Version 1.0</li>
97     * <li><code>[1.0,2.0)</code> Versions 1.0 (included) to 2.0 (not included)</li>
98     * <li><code>[1.0,2.0]</code> Versions 1.0 to 2.0 (both included)</li>
99     * <li><code>[1.5,)</code> Versions 1.5 and higher</li>
100    * <li><code>(,1.0],[1.2,)</code> Versions up to 1.0 (included) and 1.2 or
101    * higher</li>
102    * </ul>
103    * <p>
104    * If this value is specified the property
105    * {@link #isCurrentReleaseVersionOnly()} is overridden.
106    * </p>
107    * <p>
108    * It is required that the first specified restriction has at least one
109    * inclusive bound or is starting from version 0.0.0.
110    * </p>
111    * <ul>
112    * <li>
113    * <li><code>(,1.0)</code> is correct.
114    * <li><code>[0.5.0,1.0)</code> is correct.
115    * <li><code>(0.5.0,1.0]</code> is correct.
116    * <li><code>(0.5.0,1.0)</code> is <em>not</em> correct.
117    * </ul>
118    *
119    * @parameter
120    * @since 1.0
121    */
122   private String versionRange;
123 
124   // ... issue management infrastructure ......................................
125 
126   /**
127    * The directory to store task data information for the task cache. This is an
128    * optimization to not fetch tasks already fetched.
129    *
130    * @parameter expression="${project.build.directory}/task-data"
131    * @required
132    * @readonly
133    * @since 1.0
134    */
135   protected String buildDirectory;
136 
137   /**
138    * The directory to store task data information for the task cache. This is an
139    * optimization to not fetch tasks already fetched.
140    *
141    * @parameter expression="${issues.repositoryFacadeFactoryClass}"
142    *            default-value=
143    *            "de.smartics.maven.issues.factory.DefaultRepositoryFacadeFactory"
144    * @required
145    * @since 1.0
146    */
147   protected String repositoryFacadeFactoryClass;
148 
149   /**
150    * The factory creates repository facades to access the issues stored in the
151    * issue management system.
152    */
153   protected RepositoryFacadeFactory repositoryFacadeFactory;
154 
155   // ... issue management information .........................................
156 
157   /**
158    * The name of the issue management system to connect to.
159    *
160    * @parameter default-value="${project.issueManagement.system}"
161    * @since 1.0
162    */
163   protected String issueManagementSystem;
164 
165   /**
166    * The URL to the issue management system to connect to.
167    *
168    * @parameter default-value="${project.issueManagement.url}"
169    * @since 1.0
170    */
171   protected String issueManagementUrl;
172 
173   /**
174    * The name of the user for authentication to access a private installation of
175    * a issue management system.
176    *
177    * @parameter default-value=""
178    * @since 1.0
179    */
180   protected String issueManagementUser;
181 
182   /**
183    * The password for authentication to access a private installation of a issue
184    * management system.
185    *
186    * @parameter default-value=""
187    * @since 1.0
188    */
189   protected String issueManagementPassword;
190 
191   /**
192    * The name of the user for HTTP basic authentication to the issue management
193    * webserver.
194    *
195    * @parameter default-value=""
196    * @since 1.0
197    */
198   protected String webUser;
199 
200   /**
201    * The password for HTTP basic authentication to the issue management
202    * webserver.
203    *
204    * @parameter default-value=""
205    * @since 1.0
206    */
207   protected String webPassword;
208 
209   /**
210    * Sets the version of the task repository.
211    *
212    * @parameter
213    * @since 1.0
214    */
215   protected String repositoryVersion;
216 
217   /**
218    * Maximum number of entries to be displayed by the issue report. Use
219    * <code>-1</code> for unlimited entries.
220    *
221    * @parameter default-value=-1
222    * @since 1.0
223    */
224   private int maxEntries;
225 
226   /**
227    * Maximum number retries to connect to the issue management system.
228    *
229    * @parameter default-value=3
230    * @since 1.0
231    */
232   protected int maxRetries;
233 
234   /**
235    * The timeout in milliseconds between retries of connection attempts.
236    *
237    * @parameter default-value=7000
238    * @since 1.0
239    */
240   protected long timeout;
241 
242   /**
243    * The flag indicates whether (<code>true</code>) or not (<code>false</code>)
244    * logout problems from the task repository should be ignored.
245    * <p>
246    * This is a kind of hack that grabs for the string 'Logout' in the exception
247    * message reported from the task repository. It may be useful if the task
248    * repository's response is quite slow.
249    *
250    * @parameter default-value="false"
251    * @since 1.0
252    */
253   protected boolean ignoreLogoutProblem;
254 
255   /**
256    * Informs that the column names retrieved by the query should be logged at
257    * the end of the query. This allows to grab the keywords returned by the
258    * issue management system.
259    *
260    * @parameter default-value="false"
261    * @since 1.0
262    */
263   protected boolean logColumns;
264 
265   /**
266    * The encoding to read and write tasks.
267    *
268    * @parameter default-value="UTF-8"
269    * @since 1.0
270    */
271   private String taskEncoding;
272 
273   // ****************************** Initializer *******************************
274 
275   // ****************************** Constructors ******************************
276 
277   // ****************************** Inner Classes *****************************
278 
279   // ********************************* Methods ********************************
280 
281   // --- init -----------------------------------------------------------------
282 
283   // --- get&set --------------------------------------------------------------
284 
285   // --- business -------------------------------------------------------------
286 
287   /**
288    * {@inheritDoc}
289    *
290    * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
291    */
292   @Override
293   protected void executeReport(final Locale locale) throws MavenReportException
294   {
295     super.executeReport(locale);
296 
297     try
298     {
299       if (repositoryFacadeFactory == null)
300       {
301         repositoryFacadeFactory = create(repositoryFacadeFactoryClass);
302       }
303 
304       final IssueManagementConfig config = createIssueManagementConfig();
305       prepareResources();
306 
307       final VersionFactory versionFactoryInstance =
308           createInstance(versionFactory);
309       final RepositoryFacade facade = repositoryFacadeFactory.create(config);
310       final ArtifactVersionRange versionRange =
311           createVersionRange(versionFactoryInstance);
312       final QueryData queryData = createQueryData(versionRange);
313       final List<TaskData> issues = facade.queryTasks(queryData);
314       final MavenReportRenderer renderer =
315           createRenderer(locale, versionFactoryInstance, versionRange, issues);
316       renderer.render();
317     }
318     catch (final Exception e)
319     {
320       final String message = "Cannot generate " + getName(locale) + ".";
321       if (getLog().isWarnEnabled())
322       {
323         getLog().warn(message, e);
324       }
325       throw new MavenReportException(message, e);
326     }
327   }
328 
329   protected ArtifactVersionRange createVersionRange(
330       final VersionFactory versionFactoryInstance)
331   {
332     if (versionRange != null)
333     {
334       try
335       {
336         return versionFactoryInstance.createRange(versionRange);
337       }
338       catch (final InvalidVersionSpecificationException e)
339       {
340         throw new IllegalArgumentException("Cannot parse version range '"
341                                            + versionRange + "'.", e);
342       }
343     }
344     else
345     {
346       return null;
347     }
348   }
349 
350   /**
351    * Creates the query data that enables to create a query URL to be sent to the
352    * issue management system.
353    *
354    * @param versionRange the version range used to create the query data.
355    * @return an instance of query data suitable to run in a specific issue
356    *         management system.
357    */
358   protected abstract QueryData createQueryData(ArtifactVersionRange versionRange);
359 
360   /**
361    * Creates a report neutral configuration that provides information to access
362    * an issue management system.
363    *
364    * @return a valid configuration instance.
365    * @throws IllegalArgumentException if the configuration cannot be build due
366    *           to missing or invalid information.
367    */
368   private IssueManagementConfig createIssueManagementConfig()
369     throws IllegalArgumentException
370   {
371     normalizeConfigurationParameters();
372 
373     final IssueManagementConfig config =
374         new IssueManagementConfig(issueManagementSystem, issueManagementUrl);
375     config.setIgnoreLogoutProblem(ignoreLogoutProblem);
376     config.setIssueManagementPassword(issueManagementPassword);
377     config.setIssueManagementUser(issueManagementUser);
378     config.setMaxEntries(maxEntries);
379     config.setMaxRetries(maxRetries);
380     config.setTimeout(timeout);
381     config.setRepositoryVersion(repositoryVersion);
382     config.setWebPassword(webPassword);
383     config.setWebUser(webUser);
384     config.setBuildDirectory(buildDirectory);
385     config.setLogColumns(logColumns);
386     return config;
387   }
388 
389   /**
390    * Prepares the static resources if not already prepared.
391    *
392    * @throws MavenReportException if the resource cannot be prepared.
393    */
394   private void prepareResources() throws MavenReportException
395   {
396     final File taskDataDir = new File(buildDirectory);
397     if (!taskDataDir.exists())
398     {
399       final boolean created = taskDataDir.mkdirs();
400       if (!created)
401       {
402         throw new MavenReportException(
403             "Cannot create task data directory to store seralized task data.");
404       }
405     }
406 
407     TaskDataCache cache = ResourceLocator.getInstance().getTaskDataCache();
408     if (cache == null)
409     {
410       cache = new TaskDataCache(taskEncoding, taskDataDir);
411       ResourceLocator.getInstance().setTaskDataCacheIfUnset(cache);
412     }
413   }
414 
415   /**
416    * Creates a renderer.
417    *
418    * @param locale the locale to use for the report.
419    * @param issues the issues to render.
420    * @return the renderer ready to run.
421    * @throws MavenReportException if the renderer cannot be created or
422    *           configured.
423    */
424   protected abstract MavenReportRenderer createRenderer(Locale locale,
425       VersionFactory versionFactoryInstance, ArtifactVersionRange versionRange,
426       List<TaskData> issues) throws MavenReportException;
427 
428   /**
429    * Normalizes configuration parameters to ensure a successful run of the
430    * report.
431    */
432   private void normalizeConfigurationParameters()
433   {
434     if (issueManagementUrl.endsWith("/"))
435     {
436       issueManagementUrl = StringUtils.chop(issueManagementUrl);
437     }
438   }
439 
440   /**
441    * Checks if all required information is provided to run the report
442    * successfully. If the plugin is running in offline mode, this method returns
443    * <code>false</code>.
444    *
445    * @return <code>true</code> if a report can be rendered, <code>false</code>
446    *         otherwise.
447    * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport()
448    */
449   @Override
450   public boolean canGenerateReport()
451   {
452     if (offline || skip || skipReport())
453     {
454       return false;
455     }
456 
457     repositoryFacadeFactory = create(repositoryFacadeFactoryClass);
458     return validateIssueManagementConfiguration();
459   }
460 
461   private boolean skipReport()
462   {
463     final String reportId = getOutputName();
464     return this.skipReports != null && skipReports.contains(reportId);
465   }
466 
467   /**
468    * Creates the factory with the given class name.
469    *
470    * @param repositoryFacadeFactoryClassName the name of the factory to create.
471    * @return the factory of the given name.
472    * @throws IllegalArgumentException if the class cannot be instantiated.
473    */
474   @SuppressWarnings("unchecked")
475   private RepositoryFacadeFactory create(
476       final String repositoryFacadeFactoryClassName)
477     throws IllegalArgumentException
478   {
479     try
480     {
481       final Class<RepositoryFacadeFactory> clazz =
482           (Class<RepositoryFacadeFactory>) Class
483               .forName(repositoryFacadeFactoryClassName);
484       final RepositoryFacadeFactory factory = clazz.newInstance();
485       return factory;
486     }
487     catch (final Exception e)
488     {
489       throw new IllegalArgumentException("Cannot create factory class '"
490                                          + repositoryFacadeFactoryClassName
491                                          + "'.", e);
492     }
493   }
494 
495   /**
496    * Checks whether all required issue management configuration information is
497    * set.
498    * <p>
499    * The checking includes
500    * </p>
501    * <ul>
502    * <li>Is the URL to the issue management server provided?</li>
503    * <li>Is the name of the issue management system provided?</li>
504    * <li>Is the issue management server supported.</li>
505    * </ul>
506    *
507    * @return <code>true</code> if all required issue management information is
508    *         provided and valid, <code>false</code> otherwise.
509    */
510   private boolean validateIssueManagementConfiguration()
511   {
512     if (StringUtils.isBlank(issueManagementUrl))
513     {
514       getLog().error("URL to issue management system missing.");
515       return false;
516     }
517     else if (StringUtils.isBlank(issueManagementSystem))
518     {
519       getLog().error("Name of issue management system missing.");
520       return false;
521     }
522     else if (!repositoryFacadeFactory.supports(issueManagementSystem))
523     {
524       getLog().error(
525           "The issue report does not support '" + issueManagementSystem + "'.");
526       return false;
527     }
528 
529     return true;
530   }
531 
532   /**
533    * Creates the instance of a class expecting a no-args constructor.
534    *
535    * @param <T> the type of the instance to be created.
536    * @param className the name of the class to create an instance of.
537    * @return the created instance.
538    * @throws IllegalArgumentException if a problem prevents the creation of the
539    *           instance.
540    */
541   @SuppressWarnings("unchecked")
542   protected <T> T createInstance(final String className)
543     throws IllegalArgumentException
544   {
545     try
546     {
547       final Class<T> clazz = (Class<T>) Class.forName(className);
548       final T instance = clazz.newInstance();
549       return instance;
550     }
551     catch (final Exception e)
552     {
553       final String message =
554           "Cannot create instance for class '" + className + "'.";
555       final Log log = getLog();
556       if (log.isWarnEnabled())
557       {
558         log.warn(message, e);
559       }
560       throw new IllegalArgumentException(message, e);
561     }
562   }
563 
564   // --- object basics --------------------------------------------------------
565 
566 }