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