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