View Javadoc
1   package org.apache.maven.archetype.common;
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 org.apache.maven.archetype.common.util.Format;
23  import org.apache.maven.archetype.exception.InvalidPackaging;
24  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
25  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
26  import org.apache.maven.model.Model;
27  import org.apache.maven.model.Parent;
28  import org.apache.maven.model.Dependency;
29  import org.apache.maven.model.Build;
30  import org.apache.maven.model.Profile;
31  import org.apache.maven.model.ModelBase;
32  import org.apache.maven.model.Reporting;
33  import org.apache.maven.model.ReportPlugin;
34  import org.apache.maven.model.BuildBase;
35  import org.apache.maven.model.Plugin;
36  import org.codehaus.plexus.component.annotations.Component;
37  import org.codehaus.plexus.logging.AbstractLogEnabled;
38  import org.codehaus.plexus.util.FileUtils;
39  import org.codehaus.plexus.util.ReaderFactory;
40  import org.codehaus.plexus.util.StringUtils;
41  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
42  import org.codehaus.plexus.util.xml.Xpp3DomUtils;
43  import org.codehaus.plexus.util.xml.Xpp3Dom;
44  import org.dom4j.Document;
45  import org.dom4j.DocumentException;
46  import org.dom4j.Element;
47  import org.dom4j.Node;
48  import org.dom4j.io.SAXReader;
49  import org.dom4j.io.XMLWriter;
50  import org.jdom.JDOMException;
51  import org.jdom.input.SAXBuilder;
52  
53  import java.io.File;
54  import java.io.FileInputStream;
55  import java.io.FileNotFoundException;
56  import java.io.FileOutputStream;
57  import java.io.IOException;
58  import java.io.InputStream;
59  import java.io.OutputStreamWriter;
60  import java.io.Reader;
61  import java.io.StringWriter;
62  import java.io.Writer;
63  import java.util.ArrayList;
64  import java.util.HashMap;
65  import java.util.Iterator;
66  import java.util.List;
67  import java.util.Map;
68  
69  @Component( role = PomManager.class )
70  public class DefaultPomManager
71      extends AbstractLogEnabled
72      implements PomManager
73  {
74      @Override
75      public void addModule( File pom, String artifactId )
76          throws IOException, XmlPullParserException, DocumentException, InvalidPackaging
77      {
78          boolean found = false;
79  
80          StringWriter writer = new StringWriter();
81          
82          try ( Reader fileReader = ReaderFactory.newXmlReader( pom ) )
83          {
84              SAXReader reader = new SAXReader();
85              Document document = reader.read( fileReader );
86              Element project = document.getRootElement();
87  
88              String packaging = null;
89              Element packagingElement = project.element( "packaging" );
90              if ( packagingElement != null )
91              {
92                  packaging = packagingElement.getStringValue();
93              }
94              if ( !"pom".equals( packaging ) )
95              {
96                  throw new InvalidPackaging(
97                      "Unable to add module to the current project as it is not of packaging type 'pom'"
98                  );
99              }
100 
101             Element modules = project.element( "modules" );
102             if ( modules == null )
103             {
104                 modules = project.addText( "  " ).addElement( "modules" );
105                 modules.setText( "\n  " );
106                 project.addText( "\n" );
107             }
108             // TODO: change to while loop
109             for ( @SuppressWarnings( "unchecked" )
110             Iterator<Element> i = modules.elementIterator( "module" ); i.hasNext() && !found; )
111             {
112                 Element module = i.next();
113                 if ( module.getText().equals( artifactId ) )
114                 {
115                     found = true;
116                 }
117             }
118             if ( !found )
119             {
120                 Node lastTextNode = null;
121                 for ( @SuppressWarnings( "unchecked" )
122                 Iterator<Node> i = modules.nodeIterator(); i.hasNext(); )
123                 {
124                     Node node = i.next();
125                     if ( node.getNodeType() == Node.ELEMENT_NODE )
126                     {
127                         lastTextNode = null;
128                     }
129                     else if ( node.getNodeType() == Node.TEXT_NODE )
130                     {
131                         lastTextNode = node;
132                     }
133                 }
134 
135                 if ( lastTextNode != null )
136                 {
137                     modules.remove( lastTextNode );
138                 }
139 
140                 modules.addText( "\n    " );
141                 modules.addElement( "module" ).setText( artifactId );
142                 modules.addText( "\n  " );
143 
144                 XMLWriter xmlWriter = new XMLWriter( writer );
145                 xmlWriter.write( document );
146 
147                 FileUtils.fileWrite( pom.getAbsolutePath(), writer.toString() );
148             } // end if
149         }
150     }
151 
152     @Override
153     public void addParent( File pom, File parentPom )
154         throws IOException, XmlPullParserException
155     {
156         Model generatedModel = readPom( pom );
157         if ( null != generatedModel.getParent() )
158         {
159             getLogger().info( "Parent element not overwritten in " + pom );
160             return;
161         }
162 
163         Model parentModel = readPom( parentPom );
164 
165         Parent parent = new Parent();
166         parent.setGroupId( parentModel.getGroupId() );
167         if ( parent.getGroupId() == null )
168         {
169             parent.setGroupId( parentModel.getParent().getGroupId() );
170         }
171         parent.setArtifactId( parentModel.getArtifactId() );
172         parent.setVersion( parentModel.getVersion() );
173         if ( parent.getVersion() == null )
174         {
175             parent.setVersion( parentModel.getParent().getVersion() );
176         }
177         generatedModel.setParent( parent );
178 
179         writePom( generatedModel, pom, pom );
180     }
181 
182     @Override
183     public void mergePoms( File pom, File temporaryPom )
184         throws IOException, XmlPullParserException
185     {
186         Model model = readPom( pom );
187         Model generatedModel = readPom( temporaryPom );
188 
189         model.getProperties().putAll( generatedModel.getProperties() );
190 
191         mergeModelBase( model, generatedModel );
192         mergeModelBuild( model, generatedModel );
193         mergeProfiles( model, generatedModel );
194         mergeReportPlugins( model, generatedModel );
195 
196 //
197 //        // Potential merging
198 //
199 //        model.getModelEncoding ();
200 //        model.getModelVersion ();
201 //
202 //        model.getGroupId ();
203 //        model.getArtifactId ();
204 //        model.getVersion ();
205 //        model.getParent ();
206 //
207 //        model.getId ();
208 //        model.getName ();
209 //        model.getInceptionYear ();
210 //        model.getDescription ();
211 //        model.getUrl ();
212 //        model.getLicenses ();
213 //        model.getProperties ();
214 //
215 //        model.getOrganization ();
216 //        model.getMailingLists ();
217 //        model.getContributors ();
218 //        model.getDevelopers ();
219 //
220 //        model.getScm ();
221 //        model.getCiManagement ();
222 //        model.getDistributionManagement ();
223 //        model.getIssueManagement ();
224 //
225 //        model.getPackaging ();
226 ////        model.getDependencies (); // done
227 //        model.getDependencyManagement ();
228 //        model.getPrerequisites ().getMaven ();
229 //        model.getPrerequisites ().getModelEncoding ();
230 //
231 //        model.getProfiles ();
232 //        model.getModules ();
233 //        model.getRepositories ();
234 //        model.getPluginRepositories ();
235 //
236 //        model.getBuild ().getDefaultGoal ();
237 //        model.getBuild ().getFinalName ();
238 //        model.getBuild ().getModelEncoding ();
239 //        model.getBuild ().getFilters ();
240 //        model.getBuild ().getDirectory ();
241 //        model.getBuild ().getOutputDirectory ();
242 //        model.getBuild ().getSourceDirectory ();
243 //        model.getBuild ().getResources ();
244 //        model.getBuild ().getScriptSourceDirectory ();
245 //        model.getBuild ().getTestOutputDirectory ();
246 //        model.getBuild ().getTestResources ();
247 //        model.getBuild ().getTestSourceDirectory ();
248 //        model.getBuild ().getPluginManagement ();
249 //        model.getBuild ().getExtensions ();
250 ////        model.getBuild ().getPluginsAsMap (); // done
251 //
252 //        model.getReporting ().getModelEncoding ();
253 //        model.getReporting ().getOutputDirectory ();
254 ////        model.getReporting ().getReportPluginsAsMap (); // done
255 //
256 
257         writePom( model, pom, pom );
258     }
259 
260     @Override
261     public Model readPom( final File pomFile )
262         throws IOException, XmlPullParserException
263     {
264         try ( Reader pomReader = ReaderFactory.newXmlReader( pomFile ) )
265         {
266             MavenXpp3Reader reader = new MavenXpp3Reader();
267 
268             return reader.read( pomReader );
269         }
270     }
271 
272 
273     @Override
274     public Model readPom( InputStream pomStream )
275         throws IOException, XmlPullParserException
276     {
277         try ( Reader pomReader = ReaderFactory.newXmlReader( pomStream ) )
278         {
279             MavenXpp3Reader reader = new MavenXpp3Reader();
280 
281             return reader.read( pomReader );
282         }
283     }
284 
285     @Override
286     public void writePom( final Model model, final File pomFile, final File initialPomFile )
287         throws IOException
288     {
289         String fileEncoding =
290             StringUtils.isEmpty( model.getModelEncoding() ) ? "UTF-8" : model.getModelEncoding();
291 
292         org.jdom.Document doc;
293         try ( InputStream inputStream = new FileInputStream( initialPomFile ) )
294         {
295             SAXBuilder builder = new SAXBuilder();
296             doc = builder.build( inputStream );
297         }
298         catch ( JDOMException exc )
299         {
300             IOException ioe = new IOException( "Cannot parse the POM by JDOM while reading " + initialPomFile + ": "
301                                                + exc.getMessage() );
302             ioe.initCause( exc );
303             throw ioe;
304         }
305         
306         try ( Writer outputStreamWriter = new OutputStreamWriter( new FileOutputStream( pomFile ), fileEncoding ) ) 
307         {
308             // The cdata parts of the pom are not preserved from initial to target
309             MavenJDOMWriter writer = new MavenJDOMWriter();
310 
311             final String ls = System.lineSeparator();
312             Format form = Format.getRawFormat().setEncoding( fileEncoding ).setLineSeparator( ls );
313             writer.write( model, doc, outputStreamWriter, form );
314         }
315         catch ( FileNotFoundException e )
316         {
317             getLogger().debug( "Creating pom file " + pomFile );
318 
319             try ( Writer pomWriter = new OutputStreamWriter( new FileOutputStream( pomFile ), fileEncoding ) )
320             {
321                 MavenXpp3Writer writer = new MavenXpp3Writer();
322                 writer.write( pomWriter, model );
323             }
324         }
325     }
326 
327     private Map<String, Dependency> createDependencyMap( List<Dependency> dependencies )
328     {
329         Map<String, Dependency> dependencyMap = new HashMap<>();
330         for ( Dependency dependency : dependencies )
331         {
332             dependencyMap.put( dependency.getManagementKey(), dependency );
333         }
334 
335         return dependencyMap;
336     }
337 
338     private void mergeModelBuild( Model model, Model generatedModel )
339     {
340         if ( generatedModel.getBuild() != null )
341         {
342             if ( model.getBuild() == null )
343             {
344                 model.setBuild( new Build() );
345             }
346 
347             mergeBuildPlugins( model.getBuild(), generatedModel.getBuild() );
348         }
349     }
350 
351     private void mergeProfiles( Model model, Model generatedModel )
352     {
353         List<Profile> generatedProfiles = generatedModel.getProfiles();
354         if ( generatedProfiles != null && generatedProfiles.size() > 0 )
355         {
356             List<Profile> modelProfiles = model.getProfiles();
357             Map<String, Profile> modelProfileIdMap = new HashMap<>();
358             if ( modelProfiles == null )
359             {
360                 modelProfiles = new ArrayList<>();
361                 model.setProfiles( modelProfiles );
362             }
363             else if ( modelProfiles.size() > 0 )
364             {
365                 // add profile ids from the model for later lookups to the modelProfileIds set
366                 for ( Profile modelProfile : modelProfiles )
367                 {
368                     modelProfileIdMap.put( modelProfile.getId(), modelProfile );
369                 }
370             }
371 
372             for ( Profile generatedProfile : generatedProfiles )
373             {
374                 String generatedProfileId = generatedProfile.getId();
375                 if ( !modelProfileIdMap.containsKey( generatedProfileId ) )
376                 {
377                     model.addProfile( generatedProfile );
378                 }
379                 else
380                 {
381                     getLogger().warn( "Try to merge profiles with id " + generatedProfileId );
382                     mergeModelBase( modelProfileIdMap.get( generatedProfileId ), generatedProfile );
383                     mergeProfileBuild( modelProfileIdMap.get( generatedProfileId ), generatedProfile );
384                 }
385             }
386         }
387     }
388 
389     private void mergeProfileBuild( Profile modelProfile, Profile generatedProfile )
390     {
391         if ( generatedProfile.getBuild() != null )
392         {
393             if ( modelProfile.getBuild() == null )
394             {
395                 modelProfile.setBuild( new Build() );
396             }
397             mergeBuildPlugins( modelProfile.getBuild(), generatedProfile.getBuild() );
398             // TODO: merge more than just plugins in the profile...
399         }
400     }
401 
402     private void mergeModelBase( ModelBase model, ModelBase generatedModel )
403     {
404         // ModelBase can be a Model or a Profile...
405         Map<String, Dependency> dependenciesByIds = createDependencyMap( model.getDependencies() );
406 
407         Map<String, Dependency> generatedDependenciesByIds = createDependencyMap( generatedModel.getDependencies() );
408 
409         for ( String generatedDependencyId : generatedDependenciesByIds.keySet() )
410         {
411             if ( !dependenciesByIds.containsKey( generatedDependencyId ) )
412             {
413                 model.addDependency( generatedDependenciesByIds.get( generatedDependencyId ) );
414             }
415             else
416             {
417                 getLogger().warn( "Can not override property: " + generatedDependencyId );
418             }
419 
420         // TODO: maybe warn, if a property key gets overridden?
421         model.getProperties().putAll( generatedModel.getProperties() );
422 
423         // TODO: maybe merge more than just dependencies and properties...
424         }
425     }
426 
427     private void mergeReportPlugins( Model model, Model generatedModel )
428     {
429         if ( generatedModel.getReporting() != null )
430         {
431             if ( model.getReporting() == null )
432             {
433                 model.setReporting( new Reporting() );
434             }
435 
436             Map<String, ReportPlugin> reportPluginsByIds = model.getReporting().getReportPluginsAsMap();
437 
438             Map<String, ReportPlugin> generatedReportPluginsByIds =
439                 generatedModel.getReporting().getReportPluginsAsMap();
440 
441             for ( String generatedReportPluginsId : generatedReportPluginsByIds.keySet() )
442             {
443                 if ( !reportPluginsByIds.containsKey( generatedReportPluginsId ) )
444                 {
445                     model.getReporting().addPlugin( generatedReportPluginsByIds.get( generatedReportPluginsId ) );
446                 }
447                 else
448                 {
449                     getLogger().warn( "Can not override report: " + generatedReportPluginsId );
450                 }
451             }
452         }
453     }
454 
455     private void mergeBuildPlugins( BuildBase modelBuild, BuildBase generatedModelBuild )
456     {
457         Map<String, Plugin> pluginsByIds = modelBuild.getPluginsAsMap();
458 
459         List<Plugin> generatedPlugins = generatedModelBuild.getPlugins();
460 
461         for ( Plugin generatedPlugin : generatedPlugins )
462         {
463             String generatedPluginsId = generatedPlugin.getKey();
464 
465             if ( !pluginsByIds.containsKey( generatedPluginsId ) )
466             {
467                 modelBuild.addPlugin( generatedPlugin );
468             }
469             else
470             {
471                 getLogger().info( "Try to merge plugin configuration of plugins with id: " + generatedPluginsId );
472                 Plugin modelPlugin = pluginsByIds.get( generatedPluginsId );
473 
474                 modelPlugin.setConfiguration( Xpp3DomUtils.mergeXpp3Dom( (Xpp3Dom) generatedPlugin.getConfiguration(),
475                                                                          (Xpp3Dom) modelPlugin.getConfiguration() ) );
476             }
477         }
478     }
479 }