View Javadoc
1   package org.apache.maven.plugins.help;
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.IOException;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Properties;
30  
31  import org.apache.maven.model.InputLocation;
32  import org.apache.maven.model.Model;
33  import org.apache.maven.model.InputSource;
34  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
35  import org.apache.maven.model.io.xpp3.MavenXpp3WriterExOldSupport;
36  import org.apache.maven.plugin.MojoExecution;
37  import org.apache.maven.plugin.MojoExecution.Source;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugins.annotations.Mojo;
40  import org.apache.maven.plugins.annotations.Parameter;
41  import org.apache.maven.project.MavenProject;
42  import org.apache.maven.shared.utils.logging.MessageUtils;
43  import org.codehaus.plexus.util.StringUtils;
44  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
45  import org.codehaus.plexus.util.xml.XMLWriter;
46  import org.codehaus.plexus.util.xml.XmlWriterUtil;
47  import org.codehaus.plexus.util.xml.Xpp3Dom;
48  import org.codehaus.plexus.util.xml.pull.XmlSerializer;
49  
50  /**
51   * Displays the effective POM as an XML for this build, with the active profiles factored in, or a specified artifact.
52   * If <code>verbose</code>, a comment is added to each XML element describing the origin of the line.
53   *
54   * @since 2.0
55   */
56  @Mojo( name = "effective-pom", aggregator = true )
57  public class EffectivePomMojo
58      extends AbstractEffectiveMojo
59  {
60      // ----------------------------------------------------------------------
61      // Mojo parameters
62      // ----------------------------------------------------------------------
63  
64      /**
65       * The Maven project.
66       *
67       * @since 2.0.2
68       */
69      @Parameter( defaultValue = "${project}", readonly = true, required = true )
70      private MavenProject project;
71  
72      /**
73       * The projects in the current build. The effective-POM for
74       * each of these projects will written.
75       */
76      @Parameter( defaultValue = "${reactorProjects}", required = true, readonly = true )
77      private List<MavenProject> projects;
78  
79      /**
80       * This mojo execution, used to determine if it was launched from the lifecycle or the command-line.
81       */
82      @Parameter( defaultValue = "${mojo}", required = true, readonly = true )
83      private MojoExecution mojoExecution;
84  
85      /**
86       * The artifact for which to display the effective POM.
87       * <br>
88       * <b>Note</b>: Should respect the Maven format, i.e. <code>groupId:artifactId[:version]</code>. The
89       * latest version of the artifact will be used when no version is specified.
90       *
91       * @since 3.0.0
92       */
93      @Parameter( property = "artifact" )
94      private String artifact;
95  
96      /**
97       * Output POM input location as comments.
98       * 
99       * @since 3.2.0
100      */
101     @Parameter( property = "verbose", defaultValue = "false" )
102     private boolean verbose = false;
103 
104     // ----------------------------------------------------------------------
105     // Public methods
106     // ----------------------------------------------------------------------
107 
108     /** {@inheritDoc} */
109     public void execute()
110         throws MojoExecutionException
111     {
112         if ( StringUtils.isNotEmpty( artifact ) )
113         {
114             project = getMavenProject( artifact );
115             projects = Collections.singletonList( project );
116         }
117 
118         StringWriter w = new StringWriter();
119         String encoding = output != null ? project.getModel().getModelEncoding()
120                                 : System.getProperty( "file.encoding" );
121         XMLWriter writer =
122             new PrettyPrintXMLWriter( w, StringUtils.repeat( " ", XmlWriterUtil.DEFAULT_INDENTATION_SIZE ),
123                                       encoding, null );
124 
125         writeHeader( writer );
126 
127         if ( shouldWriteAllEffectivePOMsInReactor() )
128         {
129             // outer root element
130             writer.startElement( "projects" );
131             for ( MavenProject subProject : projects )
132             {
133                 writeEffectivePom( subProject, writer );
134             }
135             writer.endElement();
136         }
137         else
138         {
139             writeEffectivePom( project, writer );
140         }
141 
142         String effectivePom = prettyFormat( w.toString(), encoding, false );
143         if ( verbose )
144         {
145             // tweak location tracking comment, that are put on a separate line by pretty print
146             effectivePom = effectivePom.replaceAll( "(?m)>\\s+<!--}", ">  <!-- " );
147         }
148 
149         if ( output != null )
150         {
151             try
152             {
153                 writeXmlFile( output, effectivePom );
154             }
155             catch ( IOException e )
156             {
157                 throw new MojoExecutionException( "Cannot write effective-POM to output: " + output, e );
158             }
159 
160             getLog().info( "Effective-POM written to: " + output );
161         }
162         else
163         {
164             if ( MessageUtils.isColorEnabled() )
165             {
166                 // add color to comments
167                 String comment = MessageUtils.buffer().project( "<!--.-->" ).toString();
168                 int dotIndex = comment.indexOf( "." );
169                 String commentStart = comment.substring( 0, dotIndex );
170                 String commentEnd = comment.substring( dotIndex + 1 );
171                 effectivePom = effectivePom.replace( "<!--", commentStart ).replace( "-->", commentEnd );
172             }
173 
174             getLog().info( LS + "Effective POMs, after inheritance, interpolation, and profiles are applied:"
175                     + LS + LS + effectivePom + LS );
176         }
177     }
178 
179     /**
180      * Determines if all effective POMs of all the projects in the reactor should be written. When this goal is started
181      * on the command-line, it is always the case. However, when it is bound to a phase in the lifecycle, it is only the
182      * case when the current project being built is the head project in the reactor.
183      *
184      * @return <code>true</code> if all effective POMs should be written, <code>false</code> otherwise.
185      */
186     private boolean shouldWriteAllEffectivePOMsInReactor()
187     {
188         Source source = mojoExecution.getSource();
189         // [MNG-5550] For Maven < 3.2.1, the source is null, instead of LIFECYCLE: only rely on comparisons with CLI
190         return projects.size() > 1 && ( source == Source.CLI || projects.get( 0 ).equals( project ) );
191     }
192 
193     // ----------------------------------------------------------------------
194     // Private methods
195     // ----------------------------------------------------------------------
196 
197     /**
198      * Method for writing the effective pom informations of the current build.
199      *
200      * @param project the project of the current build, not null.
201      * @param writer the XML writer , not null, not null.
202      * @throws MojoExecutionException if any
203      */
204     private void writeEffectivePom( MavenProject project, XMLWriter writer )
205         throws MojoExecutionException
206     {
207         Model pom = project.getModel();
208         cleanModel( pom );
209 
210         StringWriter sWriter = new StringWriter();
211         try
212         {
213             if ( verbose )
214             {
215                 // try to use Maven core-provided xpp3 extended writer (available since Maven 3.6.1)
216                 if ( ! writeMavenXpp3WriterEx( sWriter, pom ) )
217                 {
218                     // xpp3 extended writer not provided by Maven core, use local code
219                     new EffectiveWriterExOldSupport().write( sWriter, pom );
220                 }
221             }
222             else
223             {
224                 new MavenXpp3Writer().write( sWriter, pom );
225             }
226         }
227         catch ( IOException e )
228         {
229             throw new MojoExecutionException( "Cannot serialize POM to XML.", e );
230         }
231 
232         // This removes the XML declaration written by MavenXpp3Writer
233         String effectivePom = prettyFormat( sWriter.toString(), null, true );
234 
235         writeComment( writer, "Effective POM for project '" + project.getId() + "'" );
236 
237         writer.writeMarkup( effectivePom );
238     }
239 
240     /**
241      * Apply some logic to clean the model before writing it.
242      *
243      * @param pom not null
244      */
245     private static void cleanModel( Model pom )
246     {
247         Properties properties = new SortedProperties();
248         properties.putAll( pom.getProperties() );
249         pom.setProperties( properties );
250     }
251 
252     private void warnWriteMavenXpp3WriterEx( Throwable t )
253     {
254         getLog().warn( "Unexpected exception while running Maven Model Extended Writer, "
255             + "falling back to old internal implementation.", t );
256     }
257 
258     private boolean writeMavenXpp3WriterEx( Writer writer, Model model )
259         throws IOException
260     {
261         try
262         {
263             Class<?> mavenXpp3WriterExClass = Class.forName( "org.apache.maven.model.io.xpp3.MavenXpp3WriterEx" );
264             Object mavenXpp3WriterEx = mavenXpp3WriterExClass.getDeclaredConstructor().newInstance();
265 
266             Method setStringFormatter =
267                 mavenXpp3WriterExClass.getMethod( "setStringFormatter", InputLocation.StringFormatter.class );
268             setStringFormatter.invoke( mavenXpp3WriterEx, new InputLocationStringFormatter() );
269 
270             Method write = mavenXpp3WriterExClass.getMethod( "write", Writer.class, Model.class );
271             write.invoke( mavenXpp3WriterEx, writer, model );
272 
273             return true;
274         }
275         catch ( ClassNotFoundException e )
276         {
277             // MavenXpp3WriterEx not available in running Maven version
278         }
279         catch ( NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException
280                 | InstantiationException e )
281         {
282             warnWriteMavenXpp3WriterEx( e );
283         }
284         catch ( InvocationTargetException e )
285         {
286             if ( e.getTargetException() instanceof IOException )
287             {
288                 throw (IOException) e.getTargetException();
289             }
290             else if ( e.getTargetException() instanceof RuntimeException )
291             {
292                 throw (RuntimeException) e.getTargetException();
293             }
294             warnWriteMavenXpp3WriterEx( e );
295         }
296         return false;
297     }
298 
299     private static String toString( InputLocation location )
300     {
301         InputSource source = location.getSource();
302 
303         String s = source.getModelId(); // by default, display modelId
304 
305         if ( StringUtils.isBlank( s ) || s.contains( "[unknown-version]" ) )
306         {
307             // unless it is blank or does not provide version information
308             s = source.toString();
309         }
310 
311         return '}' + s + ( ( location.getLineNumber() >= 0 ) ? ", line " + location.getLineNumber() : "" ) + ' ';
312     }
313 
314     private static class InputLocationStringFormatter
315         extends InputLocation.StringFormatter
316     {
317 
318         public String toString( InputLocation location )
319         {
320             return EffectivePomMojo.toString( location );
321         }
322 
323     }
324 
325     /**
326      * Xpp3 extended writer extension to improve default InputSource display
327      */
328     private static class EffectiveWriterExOldSupport
329         extends MavenXpp3WriterExOldSupport
330     {
331 
332         @Override
333         public String toString( InputLocation location )
334         {
335             return EffectivePomMojo.toString( location );
336         }
337 
338         @Override
339         protected void writeXpp3DomToSerializer( Xpp3Dom dom, XmlSerializer serializer )
340             throws java.io.IOException
341         {
342             // default method uses Xpp3Dom input location tracking, not available in older Maven versions
343             // use old Xpp3Dom serialization, without input location tracking
344             dom.writeToSerializer( null, serializer );
345         }
346     }
347 }