View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.archetype.common;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  import javax.xml.parsers.ParserConfigurationException;
24  import javax.xml.transform.TransformerException;
25  
26  import java.io.File;
27  import java.io.FileNotFoundException;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStreamWriter;
32  import java.io.Reader;
33  import java.io.StringWriter;
34  import java.io.Writer;
35  import java.nio.file.Files;
36  import java.util.ArrayList;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Map;
40  
41  import org.apache.maven.archetype.common.util.Format;
42  import org.apache.maven.archetype.common.util.PomUtils;
43  import org.apache.maven.archetype.exception.InvalidPackaging;
44  import org.apache.maven.archetype.old.ArchetypeTemplateProcessingException;
45  import org.apache.maven.model.Build;
46  import org.apache.maven.model.BuildBase;
47  import org.apache.maven.model.Dependency;
48  import org.apache.maven.model.Model;
49  import org.apache.maven.model.ModelBase;
50  import org.apache.maven.model.Parent;
51  import org.apache.maven.model.Plugin;
52  import org.apache.maven.model.Profile;
53  import org.apache.maven.model.ReportPlugin;
54  import org.apache.maven.model.Reporting;
55  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
56  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.StringUtils;
59  import org.codehaus.plexus.util.xml.XmlStreamReader;
60  import org.codehaus.plexus.util.xml.Xpp3Dom;
61  import org.codehaus.plexus.util.xml.Xpp3DomUtils;
62  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
63  import org.jdom2.JDOMException;
64  import org.jdom2.input.SAXBuilder;
65  import org.slf4j.Logger;
66  import org.slf4j.LoggerFactory;
67  import org.xml.sax.SAXException;
68  
69  @Named
70  @Singleton
71  public class DefaultPomManager implements PomManager {
72      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPomManager.class);
73  
74      @Override
75      public void addModule(File pom, String artifactId)
76              throws IOException, ParserConfigurationException, TransformerException, SAXException, InvalidPackaging,
77                      ArchetypeTemplateProcessingException {
78          StringWriter out = new StringWriter();
79          boolean found = PomUtils.addNewModule(artifactId, new XmlStreamReader(pom), out);
80          if (found) {
81              FileUtils.fileWrite(pom.getAbsolutePath(), out.toString());
82          }
83      }
84  
85      @Override
86      public void addParent(File pom, File parentPom) throws IOException, XmlPullParserException {
87          Model generatedModel = readPom(pom);
88          if (null != generatedModel.getParent()) {
89              LOGGER.info("Parent element not overwritten in " + pom);
90              return;
91          }
92  
93          Model parentModel = readPom(parentPom);
94  
95          Parent parent = new Parent();
96          parent.setGroupId(parentModel.getGroupId());
97          if (parent.getGroupId() == null) {
98              parent.setGroupId(parentModel.getParent().getGroupId());
99          }
100         parent.setArtifactId(parentModel.getArtifactId());
101         parent.setVersion(parentModel.getVersion());
102         if (parent.getVersion() == null) {
103             parent.setVersion(parentModel.getParent().getVersion());
104         }
105         generatedModel.setParent(parent);
106 
107         writePom(generatedModel, pom, pom);
108     }
109 
110     @Override
111     public void mergePoms(File pom, File temporaryPom) throws IOException, XmlPullParserException {
112         Model model = readPom(pom);
113         Model generatedModel = readPom(temporaryPom);
114 
115         model.getProperties().putAll(generatedModel.getProperties());
116 
117         mergeModelBase(model, generatedModel);
118         mergeModelBuild(model, generatedModel);
119         mergeProfiles(model, generatedModel);
120         mergeReportPlugins(model, generatedModel);
121 
122         //
123         //        // Potential merging
124         //
125         //        model.getModelEncoding ();
126         //        model.getModelVersion ();
127         //
128         //        model.getGroupId ();
129         //        model.getArtifactId ();
130         //        model.getVersion ();
131         //        model.getParent ();
132         //
133         //        model.getId ();
134         //        model.getName ();
135         //        model.getInceptionYear ();
136         //        model.getDescription ();
137         //        model.getUrl ();
138         //        model.getLicenses ();
139         //        model.getProperties ();
140         //
141         //        model.getOrganization ();
142         //        model.getMailingLists ();
143         //        model.getContributors ();
144         //        model.getDevelopers ();
145         //
146         //        model.getScm ();
147         //        model.getCiManagement ();
148         //        model.getDistributionManagement ();
149         //        model.getIssueManagement ();
150         //
151         //        model.getPackaging ();
152         ////        model.getDependencies (); // done
153         //        model.getDependencyManagement ();
154         //        model.getPrerequisites ().getMaven ();
155         //        model.getPrerequisites ().getModelEncoding ();
156         //
157         //        model.getProfiles ();
158         //        model.getModules ();
159         //        model.getRepositories ();
160         //        model.getPluginRepositories ();
161         //
162         //        model.getBuild ().getDefaultGoal ();
163         //        model.getBuild ().getFinalName ();
164         //        model.getBuild ().getModelEncoding ();
165         //        model.getBuild ().getFilters ();
166         //        model.getBuild ().getDirectory ();
167         //        model.getBuild ().getOutputDirectory ();
168         //        model.getBuild ().getSourceDirectory ();
169         //        model.getBuild ().getResources ();
170         //        model.getBuild ().getScriptSourceDirectory ();
171         //        model.getBuild ().getTestOutputDirectory ();
172         //        model.getBuild ().getTestResources ();
173         //        model.getBuild ().getTestSourceDirectory ();
174         //        model.getBuild ().getPluginManagement ();
175         //        model.getBuild ().getExtensions ();
176         ////        model.getBuild ().getPluginsAsMap (); // done
177         //
178         //        model.getReporting ().getModelEncoding ();
179         //        model.getReporting ().getOutputDirectory ();
180         ////        model.getReporting ().getReportPluginsAsMap (); // done
181         //
182 
183         writePom(model, pom, pom);
184     }
185 
186     @Override
187     public Model readPom(final File pomFile) throws IOException, XmlPullParserException {
188         try (Reader pomReader = new XmlStreamReader(pomFile)) {
189             MavenXpp3Reader reader = new MavenXpp3Reader();
190 
191             return reader.read(pomReader);
192         }
193     }
194 
195     @Override
196     public Model readPom(InputStream pomStream) throws IOException, XmlPullParserException {
197         try (Reader pomReader = new XmlStreamReader(pomStream)) {
198             MavenXpp3Reader reader = new MavenXpp3Reader();
199 
200             return reader.read(pomReader);
201         }
202     }
203 
204     @Override
205     public void writePom(final Model model, final File pomFile, final File initialPomFile) throws IOException {
206         String fileEncoding = StringUtils.isEmpty(model.getModelEncoding()) ? "UTF-8" : model.getModelEncoding();
207 
208         org.jdom2.Document doc;
209         try (InputStream inputStream = Files.newInputStream(initialPomFile.toPath())) {
210             SAXBuilder builder = new SAXBuilder();
211             doc = builder.build(inputStream);
212         } catch (JDOMException exc) {
213             throw new IOException(
214                     "Cannot parse the POM by JDOM while reading " + initialPomFile + ": " + exc.getMessage(), exc);
215         }
216 
217         if (!pomFile.exists() && !pomFile.createNewFile()) {
218             LOGGER.warn("Could not create new file \"" + pomFile.getPath() + "\" or the file already exists.");
219         }
220 
221         try (Writer outputStreamWriter = new OutputStreamWriter(new FileOutputStream(pomFile), fileEncoding)) {
222             // The cdata parts of the pom are not preserved from initial to target
223             MavenJDOMWriter writer = new MavenJDOMWriter();
224 
225             final String ls = System.lineSeparator();
226             Format form = Format.getRawFormat().setEncoding(fileEncoding).setLineSeparator(ls);
227             writer.write(model, doc, outputStreamWriter, form);
228         } catch (FileNotFoundException e) {
229             LOGGER.debug("Creating pom file " + pomFile);
230 
231             try (Writer pomWriter = new OutputStreamWriter(Files.newOutputStream(pomFile.toPath()), fileEncoding)) {
232                 MavenXpp3Writer writer = new MavenXpp3Writer();
233                 writer.write(pomWriter, model);
234             }
235         }
236     }
237 
238     private Map<String, Dependency> createDependencyMap(List<Dependency> dependencies) {
239         Map<String, Dependency> dependencyMap = new HashMap<>();
240         for (Dependency dependency : dependencies) {
241             dependencyMap.put(dependency.getManagementKey(), dependency);
242         }
243 
244         return dependencyMap;
245     }
246 
247     private void mergeModelBuild(Model model, Model generatedModel) {
248         if (generatedModel.getBuild() != null) {
249             if (model.getBuild() == null) {
250                 model.setBuild(new Build());
251             }
252 
253             mergeBuildPlugins(model.getBuild(), generatedModel.getBuild());
254         }
255     }
256 
257     private void mergeProfiles(Model model, Model generatedModel) {
258         List<Profile> generatedProfiles = generatedModel.getProfiles();
259         if (generatedProfiles != null && !generatedProfiles.isEmpty()) {
260             List<Profile> modelProfiles = model.getProfiles();
261             Map<String, Profile> modelProfileIdMap = new HashMap<>();
262             if (modelProfiles == null) {
263                 modelProfiles = new ArrayList<>();
264                 model.setProfiles(modelProfiles);
265             } else if (!modelProfiles.isEmpty()) {
266                 // add profile ids from the model for later lookups to the modelProfileIds set
267                 for (Profile modelProfile : modelProfiles) {
268                     modelProfileIdMap.put(modelProfile.getId(), modelProfile);
269                 }
270             }
271 
272             for (Profile generatedProfile : generatedProfiles) {
273                 String generatedProfileId = generatedProfile.getId();
274                 if (!modelProfileIdMap.containsKey(generatedProfileId)) {
275                     model.addProfile(generatedProfile);
276                 } else {
277                     LOGGER.warn("Try to merge profiles with id " + generatedProfileId);
278                     mergeModelBase(modelProfileIdMap.get(generatedProfileId), generatedProfile);
279                     mergeProfileBuild(modelProfileIdMap.get(generatedProfileId), generatedProfile);
280                 }
281             }
282         }
283     }
284 
285     private void mergeProfileBuild(Profile modelProfile, Profile generatedProfile) {
286         if (generatedProfile.getBuild() != null) {
287             if (modelProfile.getBuild() == null) {
288                 modelProfile.setBuild(new Build());
289             }
290             mergeBuildPlugins(modelProfile.getBuild(), generatedProfile.getBuild());
291             // TODO: merge more than just plugins in the profile...
292         }
293     }
294 
295     private void mergeModelBase(ModelBase model, ModelBase generatedModel) {
296         // ModelBase can be a Model or a Profile...
297         Map<String, Dependency> dependenciesByIds = createDependencyMap(model.getDependencies());
298 
299         Map<String, Dependency> generatedDependenciesByIds = createDependencyMap(generatedModel.getDependencies());
300 
301         for (String generatedDependencyId : generatedDependenciesByIds.keySet()) {
302             if (!dependenciesByIds.containsKey(generatedDependencyId)) {
303                 model.addDependency(generatedDependenciesByIds.get(generatedDependencyId));
304             } else {
305                 LOGGER.warn("Can not override property: " + generatedDependencyId);
306             }
307 
308             // TODO: maybe warn, if a property key gets overridden?
309             model.getProperties().putAll(generatedModel.getProperties());
310 
311             // TODO: maybe merge more than just dependencies and properties...
312         }
313     }
314 
315     private void mergeReportPlugins(Model model, Model generatedModel) {
316         if (generatedModel.getReporting() != null) {
317             if (model.getReporting() == null) {
318                 model.setReporting(new Reporting());
319             }
320 
321             Map<String, ReportPlugin> reportPluginsByIds = model.getReporting().getReportPluginsAsMap();
322 
323             Map<String, ReportPlugin> generatedReportPluginsByIds =
324                     generatedModel.getReporting().getReportPluginsAsMap();
325 
326             for (String generatedReportPluginsId : generatedReportPluginsByIds.keySet()) {
327                 if (!reportPluginsByIds.containsKey(generatedReportPluginsId)) {
328                     model.getReporting().addPlugin(generatedReportPluginsByIds.get(generatedReportPluginsId));
329                 } else {
330                     LOGGER.warn("Can not override report: " + generatedReportPluginsId);
331                 }
332             }
333         }
334     }
335 
336     private void mergeBuildPlugins(BuildBase modelBuild, BuildBase generatedModelBuild) {
337         Map<String, Plugin> pluginsByIds = modelBuild.getPluginsAsMap();
338 
339         List<Plugin> generatedPlugins = generatedModelBuild.getPlugins();
340 
341         for (Plugin generatedPlugin : generatedPlugins) {
342             String generatedPluginsId = generatedPlugin.getKey();
343 
344             if (!pluginsByIds.containsKey(generatedPluginsId)) {
345                 modelBuild.addPlugin(generatedPlugin);
346             } else {
347                 LOGGER.info("Try to merge plugin configuration of plugins with id: " + generatedPluginsId);
348                 Plugin modelPlugin = pluginsByIds.get(generatedPluginsId);
349 
350                 modelPlugin.setConfiguration(Xpp3DomUtils.mergeXpp3Dom(
351                         (Xpp3Dom) generatedPlugin.getConfiguration(), (Xpp3Dom) modelPlugin.getConfiguration()));
352             }
353         }
354     }
355 }