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   // ****************************** Initializer *******************************
266 
267   // ****************************** Constructors ******************************
268 
269   // ****************************** Inner Classes *****************************
270 
271   // ********************************* Methods ********************************
272 
273   // --- init -----------------------------------------------------------------
274 
275   // --- get&set --------------------------------------------------------------
276 
277   // --- business -------------------------------------------------------------
278 
279   /**
280    * {@inheritDoc}
281    *
282    * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
283    */
284   @Override
285   protected void executeReport(final Locale locale) throws MavenReportException
286   {
287     super.executeReport(locale);
288 
289     try
290     {
291       if (repositoryFacadeFactory == null)
292       {
293         repositoryFacadeFactory = create(repositoryFacadeFactoryClass);
294       }
295 
296       final IssueManagementConfig config = createIssueManagementConfig();
297       prepareResources();
298 
299       final VersionFactory versionFactoryInstance =
300           createInstance(versionFactory);
301       final RepositoryFacade facade = repositoryFacadeFactory.create(config);
302       final ArtifactVersionRange versionRange =
303           createVersionRange(versionFactoryInstance);
304       final QueryData queryData = createQueryData(versionRange);
305       final List<TaskData> issues = facade.queryTasks(queryData);
306       final MavenReportRenderer renderer =
307           createRenderer(locale, versionFactoryInstance, versionRange, issues);
308       renderer.render();
309     }
310     catch (final Exception e)
311     {
312       final String message = "Cannot generate " + getName(locale) + ".";
313       if (getLog().isWarnEnabled())
314       {
315         getLog().warn(message, e);
316       }
317       throw new MavenReportException(message, e);
318     }
319   }
320 
321   protected ArtifactVersionRange createVersionRange(
322       final VersionFactory versionFactoryInstance)
323   {
324     if (versionRange != null)
325     {
326       try
327       {
328         return versionFactoryInstance.createRange(versionRange);
329       }
330       catch (final InvalidVersionSpecificationException e)
331       {
332         throw new IllegalArgumentException("Cannot parse version range '"
333                                            + versionRange + "'.", e);
334       }
335     }
336     else
337     {
338       return null;
339     }
340   }
341 
342   /**
343    * Creates the query data that enables to create a query URL to be sent to the
344    * issue management system.
345    *
346    * @param versionRange the version range used to create the query data.
347    * @return an instance of query data suitable to run in a specific issue
348    *         management system.
349    */
350   protected abstract QueryData createQueryData(ArtifactVersionRange versionRange);
351 
352   /**
353    * Creates a report neutral configuration that provides information to access
354    * an issue management system.
355    *
356    * @return a valid configuration instance.
357    * @throws IllegalArgumentException if the configuration cannot be build due
358    *           to missing or invalid information.
359    */
360   private IssueManagementConfig createIssueManagementConfig()
361     throws IllegalArgumentException
362   {
363     normalizeConfigurationParameters();
364 
365     final IssueManagementConfig config =
366         new IssueManagementConfig(issueManagementSystem, issueManagementUrl);
367     config.setIgnoreLogoutProblem(ignoreLogoutProblem);
368     config.setIssueManagementPassword(issueManagementPassword);
369     config.setIssueManagementUser(issueManagementUser);
370     config.setMaxEntries(maxEntries);
371     config.setMaxRetries(maxRetries);
372     config.setTimeout(timeout);
373     config.setRepositoryVersion(repositoryVersion);
374     config.setWebPassword(webPassword);
375     config.setWebUser(webUser);
376     config.setBuildDirectory(buildDirectory);
377     config.setLogColumns(logColumns);
378     return config;
379   }
380 
381   /**
382    * Prepares the static resources if not already prepared.
383    *
384    * @throws MavenReportException if the resource cannot be prepared.
385    */
386   private void prepareResources() throws MavenReportException
387   {
388     final File taskDataDir = new File(buildDirectory);
389     if (!taskDataDir.exists())
390     {
391       final boolean created = taskDataDir.mkdirs();
392       if (!created)
393       {
394         throw new MavenReportException(
395             "Cannot create task data directory to store seralized task data.");
396       }
397     }
398 
399     TaskDataCache cache = ResourceLocator.getInstance().getTaskDataCache();
400     if (cache == null)
401     {
402       cache = new TaskDataCache(taskDataDir);
403       ResourceLocator.getInstance().setTaskDataCacheIfUnset(cache);
404     }
405   }
406 
407   /**
408    * Creates a renderer.
409    *
410    * @param locale the locale to use for the report.
411    * @param issues the issues to render.
412    * @return the renderer ready to run.
413    * @throws MavenReportException if the renderer cannot be created or
414    *           configured.
415    */
416   protected abstract MavenReportRenderer createRenderer(Locale locale,
417       VersionFactory versionFactoryInstance, ArtifactVersionRange versionRange,
418       List<TaskData> issues) throws MavenReportException;
419 
420   /**
421    * Normalizes configuration parameters to ensure a successful run of the
422    * report.
423    */
424   private void normalizeConfigurationParameters()
425   {
426     if (issueManagementUrl.endsWith("/"))
427     {
428       issueManagementUrl = StringUtils.chop(issueManagementUrl);
429     }
430   }
431 
432   /**
433    * Checks if all required information is provided to run the report
434    * successfully. If the plugin is running in offline mode, this method returns
435    * <code>false</code>.
436    *
437    * @return <code>true</code> if a report can be rendered, <code>false</code>
438    *         otherwise.
439    * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport()
440    */
441   @Override
442   public boolean canGenerateReport()
443   {
444     if (offline || skip || skipReport())
445     {
446       return false;
447     }
448 
449     repositoryFacadeFactory = create(repositoryFacadeFactoryClass);
450     return validateIssueManagementConfiguration();
451   }
452 
453   private boolean skipReport()
454   {
455     final String reportId = getOutputName();
456     return this.skipReports != null && skipReports.contains(reportId);
457   }
458 
459   /**
460    * Creates the factory with the given class name.
461    *
462    * @param repositoryFacadeFactoryClassName the name of the factory to create.
463    * @return the factory of the given name.
464    * @throws IllegalArgumentException if the class cannot be instantiated.
465    */
466   @SuppressWarnings("unchecked")
467   private RepositoryFacadeFactory create(
468       final String repositoryFacadeFactoryClassName)
469     throws IllegalArgumentException
470   {
471     try
472     {
473       final Class<RepositoryFacadeFactory> clazz =
474           (Class<RepositoryFacadeFactory>) Class
475               .forName(repositoryFacadeFactoryClassName);
476       final RepositoryFacadeFactory factory = clazz.newInstance();
477       return factory;
478     }
479     catch (final Exception e)
480     {
481       throw new IllegalArgumentException("Cannot create factory class '"
482                                          + repositoryFacadeFactoryClassName
483                                          + "'.", e);
484     }
485   }
486 
487   /**
488    * Checks whether all required issue management configuration information is
489    * set.
490    * <p>
491    * The checking includes
492    * </p>
493    * <ul>
494    * <li>Is the URL to the issue management server provided?</li>
495    * <li>Is the name of the issue management system provided?</li>
496    * <li>Is the issue management server supported.</li>
497    * </ul>
498    *
499    * @return <code>true</code> if all required issue management information is
500    *         provided and valid, <code>false</code> otherwise.
501    */
502   private boolean validateIssueManagementConfiguration()
503   {
504     if (StringUtils.isBlank(issueManagementUrl))
505     {
506       getLog().error("URL to issue management system missing.");
507       return false;
508     }
509     else if (StringUtils.isBlank(issueManagementSystem))
510     {
511       getLog().error("Name of issue management system missing.");
512       return false;
513     }
514     else if (!repositoryFacadeFactory.supports(issueManagementSystem))
515     {
516       getLog().error(
517           "The issue report does not support '" + issueManagementSystem + "'.");
518       return false;
519     }
520 
521     return true;
522   }
523 
524   /**
525    * Creates the instance of a class expecting a no-args constructor.
526    *
527    * @param <T> the type of the instance to be created.
528    * @param className the name of the class to create an instance of.
529    * @return the created instance.
530    * @throws IllegalArgumentException if a problem prevents the creation of the
531    *           instance.
532    */
533   @SuppressWarnings("unchecked")
534   protected <T> T createInstance(final String className)
535     throws IllegalArgumentException
536   {
537     try
538     {
539       final Class<T> clazz = (Class<T>) Class.forName(className);
540       final T instance = clazz.newInstance();
541       return instance;
542     }
543     catch (final Exception e)
544     {
545       final String message =
546           "Cannot create instance for class '" + className + "'.";
547       final Log log = getLog();
548       if (log.isWarnEnabled())
549       {
550         log.warn(message, e);
551       }
552       throw new IllegalArgumentException(message, e);
553     }
554   }
555 
556   // --- object basics --------------------------------------------------------
557 
558 }