View Javadoc

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