View Javadoc
1   package org.apache.maven.report.projectinfo;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStreamWriter;
26  import java.io.Writer;
27  import java.net.MalformedURLException;
28  import java.net.URL;
29  import java.net.URLClassLoader;
30  import java.text.MessageFormat;
31  import java.util.Collection;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.Map;
36  import java.util.MissingResourceException;
37  import java.util.ResourceBundle;
38  
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.artifact.factory.ArtifactFactory;
41  import org.apache.maven.artifact.repository.ArtifactRepository;
42  import org.apache.maven.artifact.resolver.ArtifactResolver;
43  import org.apache.maven.doxia.site.decoration.Body;
44  import org.apache.maven.doxia.site.decoration.DecorationModel;
45  import org.apache.maven.doxia.siterenderer.Renderer;
46  import org.apache.maven.doxia.siterenderer.RendererException;
47  import org.apache.maven.doxia.siterenderer.RenderingContext;
48  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
49  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
50  import org.apache.maven.doxia.tools.SiteTool;
51  import org.apache.maven.doxia.tools.SiteToolException;
52  import org.apache.maven.model.Plugin;
53  import org.apache.maven.plugin.MojoExecutionException;
54  import org.apache.maven.plugins.annotations.Component;
55  import org.apache.maven.plugins.annotations.Parameter;
56  import org.apache.maven.project.MavenProject;
57  import org.apache.maven.project.MavenProjectBuilder;
58  import org.apache.maven.reporting.AbstractMavenReport;
59  import org.apache.maven.reporting.MavenReportException;
60  import org.apache.maven.settings.Settings;
61  import org.codehaus.plexus.i18n.I18N;
62  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
63  import org.codehaus.plexus.interpolation.InterpolationException;
64  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
65  import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
66  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
67  import org.codehaus.plexus.util.IOUtil;
68  import org.codehaus.plexus.util.StringUtils;
69  import org.codehaus.plexus.util.xml.Xpp3Dom;
70  
71  /**
72   * Base class with the things that should be in AbstractMavenReport anyway.
73   *
74   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
75   * @version $Id: AbstractProjectInfoReport.html 935177 2015-01-05 21:05:55Z michaelo $
76   * @since 2.0
77   */
78  public abstract class AbstractProjectInfoReport
79      extends AbstractMavenReport
80  {
81      // ----------------------------------------------------------------------
82      // Mojo components
83      // ----------------------------------------------------------------------
84  
85      /**
86       * SiteTool component.
87       *
88       * @since 2.1
89       */
90      @Component
91      protected SiteTool siteTool;
92  
93      /**
94       * Doxia Site Renderer component.
95       */
96      @Component
97      protected Renderer siteRenderer;
98  
99      /**
100      * Artifact Resolver component.
101      */
102     @Component
103     protected ArtifactResolver resolver;
104 
105     /**
106      * Artifact Factory component.
107      */
108     @Component
109     protected ArtifactFactory factory;
110 
111     /**
112      * Internationalization component, could support also custom bundle using {@link #customBundle}.
113      */
114     @Component
115     private I18N i18n;
116 
117     /**
118      * Project builder.
119      */
120     @Component
121     protected MavenProjectBuilder mavenProjectBuilder;
122 
123     // ----------------------------------------------------------------------
124     // Mojo parameters
125     // ----------------------------------------------------------------------
126 
127     /**
128      * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from
129      * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in
130      * the Maven Site Plugin is used instead.
131      */
132     @Parameter( property = "project.reporting.outputDirectory", required = true )
133     protected File outputDirectory;
134 
135     /**
136      * The Maven Project.
137      */
138     @Parameter( defaultValue = "${project}", readonly = true, required = true )
139     protected MavenProject project;
140 
141     /**
142      * Local Repository.
143      */
144     @Parameter( property = "localRepository", required = true, readonly = true )
145     protected ArtifactRepository localRepository;
146 
147     /**
148      * Remote repositories used for the project.
149      *
150      * @since 2.1
151      */
152     @Parameter( property = "project.remoteArtifactRepositories" )
153     protected List<ArtifactRepository> remoteRepositories;
154 
155     /**
156      * The current user system settings for use in Maven.
157      *
158      * @since 2.3
159      */
160     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
161     protected Settings settings;
162 
163     /**
164      * Path for a custom bundle instead of using the default one. <br/>
165      * Using this field, you could change the texts in the generated reports.
166      *
167      * @since 2.3
168      */
169     @Parameter( defaultValue = "${project.basedir}/src/site/custom/project-info-report.properties" )
170     protected String customBundle;
171 
172     /**
173      * Skip report.
174      *
175      * @since 2.8
176      */
177     @Parameter( property = "mpir.skip", defaultValue = "false" )
178     private boolean skip;
179 
180     /**
181      * Skip the project info report generation if a report-specific section of the POM is empty.
182      * Defaults to <code>true</code>.
183      *
184      * @since 2.8
185      */
186     @Parameter( defaultValue = "true" )
187     protected boolean skipEmptyReport;
188 
189     // ----------------------------------------------------------------------
190     // Public methods
191     // ----------------------------------------------------------------------
192 
193     @Override
194     public boolean canGenerateReport()
195     {
196         return !skip;
197     }
198 
199     @Override
200     public void execute()
201         throws MojoExecutionException
202     {
203         if ( !canGenerateReport() )
204         {
205             return;
206         }
207 
208         // TODO: push to a helper? Could still be improved by taking more of the site information from the site plugin
209         Writer writer = null;
210         try
211         {
212             String filename = getOutputName() + ".html";
213 
214             DecorationModel model = new DecorationModel();
215             model.setBody( new Body() );
216 
217             Map<String, Object> attributes = new HashMap<String, Object>();
218             attributes.put( "outputEncoding", "UTF-8" );
219             attributes.put( "project", project );
220 
221             Locale locale = Locale.getDefault();
222             Artifact defaultSkin =
223                 siteTool.getDefaultSkinArtifact( localRepository, project.getRemoteArtifactRepositories() );
224 
225             // CHECKSTYLE_OFF: LineLength
226             SiteRenderingContext siteContext =
227                 siteRenderer.createContextForSkin( defaultSkin.getFile(), attributes, model, getName( locale ), locale );
228             // CHECKSTYLE_ON: LineLength
229 
230             RenderingContext context = new RenderingContext( outputDirectory, filename );
231 
232             SiteRendererSink sink = new SiteRendererSink( context );
233 
234             generate( sink, null, locale );
235 
236             outputDirectory.mkdirs();
237 
238             writer = new OutputStreamWriter( new FileOutputStream( new File( outputDirectory, filename ) ), "UTF-8" );
239 
240             siteRenderer.generateDocument( writer, sink, siteContext );
241 
242             siteRenderer.copyResources( siteContext, new File( project.getBasedir(), "src/site/resources" ),
243                                         outputDirectory );
244         }
245         catch ( RendererException e )
246         {
247             throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
248                 + " report generation.", e );
249         }
250         catch ( IOException e )
251         {
252             throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
253                 + " report generation.", e );
254         }
255         catch ( SiteToolException e )
256         {
257             throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
258                 + " report generation.", e );
259         }
260         catch ( MavenReportException e )
261         {
262             throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
263                 + " report generation.", e );
264         }
265         finally
266         {
267             IOUtil.close( writer );
268         }
269     }
270 
271     @Override
272     public String getCategoryName()
273     {
274         return CATEGORY_PROJECT_INFORMATION;
275     }
276 
277     // ----------------------------------------------------------------------
278     // Protected methods
279     // ----------------------------------------------------------------------
280 
281     /**
282      * @param coll The collection to be checked.
283      * @return true if coll is empty false otherwise.
284      */
285     protected boolean isEmpty( Collection<?> coll )
286     {
287         return coll == null || coll.isEmpty();
288     }
289 
290     @Override
291     protected String getOutputDirectory()
292     {
293         return outputDirectory.getAbsolutePath();
294     }
295 
296     @Override
297     public File getReportOutputDirectory()
298     {
299         return outputDirectory;
300     }
301 
302     @Override
303     public void setReportOutputDirectory( File reportOutputDirectory )
304     {
305         this.outputDirectory = reportOutputDirectory;
306     }
307 
308     @Override
309     protected MavenProject getProject()
310     {
311         return project;
312     }
313 
314     /**
315      * @param pluginId The id of the plugin
316      * @return The information about the plugin.
317      */
318     protected Plugin getPlugin( String pluginId )
319     {
320         if ( ( getProject().getBuild() == null ) || ( getProject().getBuild().getPluginsAsMap() == null ) )
321         {
322             return null;
323         }
324 
325         Plugin plugin = (Plugin) getProject().getBuild().getPluginsAsMap().get( pluginId );
326 
327         if ( ( plugin == null ) && ( getProject().getBuild().getPluginManagement() != null )
328             && ( getProject().getBuild().getPluginManagement().getPluginsAsMap() != null ) )
329         {
330             plugin = (Plugin) getProject().getBuild().getPluginManagement().getPluginsAsMap().get( pluginId );
331         }
332 
333         return plugin;
334     }
335 
336     /**
337      * @param pluginId The pluginId
338      * @param param The child which should be checked.
339      * @return The value of the dom tree.
340      */
341     protected String getPluginParameter( String pluginId, String param )
342     {
343         Plugin plugin = getPlugin( pluginId );
344         if ( plugin != null )
345         {
346             Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
347             if ( xpp3Dom != null && xpp3Dom.getChild( param ) != null
348                 && StringUtils.isNotEmpty( xpp3Dom.getChild( param ).getValue() ) )
349             {
350                 return xpp3Dom.getChild( param ).getValue();
351             }
352         }
353 
354         return null;
355     }
356 
357     /**
358      * {@inheritDoc}
359      */
360     @Override
361     protected Renderer getSiteRenderer()
362     {
363         return siteRenderer;
364     }
365 
366     /**
367      * @param locale The locale
368      * @param key The key to search for
369      * @return The text appropriate for the locale.
370      */
371     protected String getI18nString( Locale locale, String key )
372     {
373         return getI18N( locale ).getString( "project-info-report", locale, "report." + getI18Nsection() + '.' + key );
374     }
375 
376     /**
377      * @param locale The local.
378      * @return I18N for the locale
379      */
380     protected I18N getI18N( Locale locale )
381     {
382         if ( customBundle != null )
383         {
384             File customBundleFile = new File( customBundle );
385             if ( customBundleFile.isFile() && customBundleFile.getName().endsWith( ".properties" ) )
386             {
387                 if ( !i18n.getClass().isAssignableFrom( CustomI18N.class ) )
388                 {
389                     // first load
390                     i18n = new CustomI18N( project, settings, customBundleFile, locale, i18n );
391                 }
392                 else if ( !i18n.getDefaultLanguage().equals( locale.getLanguage() ) )
393                 {
394                     i18n = new CustomI18N( project, settings, customBundleFile, locale, i18n );
395                 }
396             }
397         }
398 
399         return i18n;
400     }
401 
402     /**
403      * @return The according string for the section.
404      */
405     protected abstract String getI18Nsection();
406 
407     /** {@inheritDoc} */
408     public String getName( Locale locale )
409     {
410         return getI18nString( locale, "name" );
411     }
412 
413     /** {@inheritDoc} */
414     public String getDescription( Locale locale )
415     {
416         return getI18nString( locale, "description" );
417     }
418 
419     private static class CustomI18N
420         implements I18N
421     {
422         private final MavenProject project;
423 
424         private final Settings settings;
425 
426         private final String bundleName;
427 
428         private final Locale locale;
429 
430         private final I18N i18nOriginal;
431 
432         private ResourceBundle bundle;
433 
434         private static final Object[] NO_ARGS = new Object[0];
435 
436         public CustomI18N( MavenProject project, Settings settings, File customBundleFile, Locale locale,
437                            I18N i18nOriginal )
438         {
439             super();
440             this.project = project;
441             this.settings = settings;
442             this.locale = locale;
443             this.i18nOriginal = i18nOriginal;
444             this.bundleName =
445                 customBundleFile.getName().substring( 0, customBundleFile.getName().indexOf( ".properties" ) );
446 
447             URLClassLoader classLoader = null;
448             try
449             {
450                 classLoader = new URLClassLoader( new URL[] { customBundleFile.getParentFile().toURI().toURL() } );
451             }
452             catch ( MalformedURLException e )
453             {
454                 //could not happen.
455             }
456 
457             this.bundle = ResourceBundle.getBundle( this.bundleName, locale, classLoader );
458             if ( !this.bundle.getLocale().getLanguage().equals( locale.getLanguage() ) )
459             {
460                 this.bundle = ResourceBundle.getBundle( this.bundleName, Locale.getDefault(), classLoader );
461             }
462         }
463 
464         /** {@inheritDoc} */
465         public String getDefaultLanguage()
466         {
467             return locale.getLanguage();
468         }
469 
470         /** {@inheritDoc} */
471         public String getDefaultCountry()
472         {
473             return locale.getCountry();
474         }
475 
476         /** {@inheritDoc} */
477         public String getDefaultBundleName()
478         {
479             return bundleName;
480         }
481 
482         /** {@inheritDoc} */
483         public String[] getBundleNames()
484         {
485             return new String[] { bundleName };
486         }
487 
488         /** {@inheritDoc} */
489         public ResourceBundle getBundle()
490         {
491             return bundle;
492         }
493 
494         /** {@inheritDoc} */
495         public ResourceBundle getBundle( String bundleName )
496         {
497             return bundle;
498         }
499 
500         /** {@inheritDoc} */
501         public ResourceBundle getBundle( String bundleName, String languageHeader )
502         {
503             return bundle;
504         }
505 
506         /** {@inheritDoc} */
507         public ResourceBundle getBundle( String bundleName, Locale locale )
508         {
509             return bundle;
510         }
511 
512         /** {@inheritDoc} */
513         public Locale getLocale( String languageHeader )
514         {
515             return new Locale( languageHeader );
516         }
517 
518         /** {@inheritDoc} */
519         public String getString( String key )
520         {
521             return getString( bundleName, locale, key );
522         }
523 
524         /** {@inheritDoc} */
525         public String getString( String key, Locale locale )
526         {
527             return getString( bundleName, locale, key );
528         }
529 
530         /** {@inheritDoc} */
531         public String getString( String bundleName, Locale locale, String key )
532         {
533             String value;
534 
535             if ( locale == null )
536             {
537                 locale = getLocale( null );
538             }
539 
540             ResourceBundle rb = getBundle( bundleName, locale );
541             value = getStringOrNull( rb, key );
542 
543             if ( value == null )
544             {
545                 // try to load default
546                 value = i18nOriginal.getString( bundleName, locale, key );
547             }
548 
549             if ( !value.contains( "${" ) )
550             {
551                 return value;
552             }
553 
554             final RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
555             try
556             {
557                 interpolator.addValueSource( new EnvarBasedValueSource() );
558             }
559             catch ( final IOException e )
560             {
561                 //In which cases could this happen? And what should we do?
562             }
563 
564             interpolator.addValueSource( new PropertiesBasedValueSource( System.getProperties() ) );
565             interpolator.addValueSource( new PropertiesBasedValueSource( project.getProperties() ) );
566             interpolator.addValueSource( new PrefixedObjectValueSource( "project", project ) );
567             interpolator.addValueSource( new PrefixedObjectValueSource( "pom", project ) );
568             interpolator.addValueSource( new PrefixedObjectValueSource( "settings", settings ) );
569 
570             try
571             {
572                 value = interpolator.interpolate( value );
573             }
574             catch ( final InterpolationException e )
575             {
576                 //What does this exception mean?
577             }
578 
579             return value;
580         }
581 
582         /** {@inheritDoc} */
583         public String format( String key, Object arg1 )
584         {
585             return format( bundleName, locale, key, new Object[] { arg1 } );
586         }
587 
588         /** {@inheritDoc} */
589         public String format( String key, Object arg1, Object arg2 )
590         {
591             return format( bundleName, locale, key, new Object[] { arg1, arg2 } );
592         }
593 
594         /** {@inheritDoc} */
595         public String format( String bundleName, Locale locale, String key, Object arg1 )
596         {
597             return format( bundleName, locale, key, new Object[] { arg1 } );
598         }
599 
600         /** {@inheritDoc} */
601         public String format( String bundleName, Locale locale, String key, Object arg1, Object arg2 )
602         {
603             return format( bundleName, locale, key, new Object[] { arg1, arg2 } );
604         }
605 
606         /** {@inheritDoc} */
607         public String format( String bundleName, Locale locale, String key, Object[] args )
608         {
609             if ( locale == null )
610             {
611                 locale = getLocale( null );
612             }
613 
614             String value = getString( bundleName, locale, key );
615             if ( args == null )
616             {
617                 args = NO_ARGS;
618             }
619 
620             MessageFormat messageFormat = new MessageFormat( "" );
621             messageFormat.setLocale( locale );
622             messageFormat.applyPattern( value );
623 
624             return messageFormat.format( args );
625         }
626 
627         private String getStringOrNull( ResourceBundle rb, String key )
628         {
629             if ( rb != null )
630             {
631                 try
632                 {
633                     return rb.getString( key );
634                 }
635                 catch ( MissingResourceException ignored )
636                 {
637                     // intentional
638                 }
639             }
640             return null;
641         }
642     }
643 }