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