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.project.inheritance;
20  
21  import java.util.ArrayList;
22  import java.util.LinkedHashMap;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Properties;
27  import java.util.StringTokenizer;
28  import java.util.TreeMap;
29  
30  import org.apache.maven.model.Build;
31  import org.apache.maven.model.Dependency;
32  import org.apache.maven.model.DependencyManagement;
33  import org.apache.maven.model.DeploymentRepository;
34  import org.apache.maven.model.DistributionManagement;
35  import org.apache.maven.model.Extension;
36  import org.apache.maven.model.Model;
37  import org.apache.maven.model.PluginManagement;
38  import org.apache.maven.model.ReportPlugin;
39  import org.apache.maven.model.ReportSet;
40  import org.apache.maven.model.Reporting;
41  import org.apache.maven.model.Resource;
42  import org.apache.maven.model.Scm;
43  import org.apache.maven.model.Site;
44  import org.apache.maven.project.ModelUtils;
45  import org.codehaus.plexus.component.annotations.Component;
46  import org.codehaus.plexus.util.StringUtils;
47  import org.codehaus.plexus.util.xml.Xpp3Dom;
48  
49  /**
50   * DefaultModelInheritanceAssembler
51   */
52  @Component(role = ModelInheritanceAssembler.class)
53  public class DefaultModelInheritanceAssembler implements ModelInheritanceAssembler {
54      // TODO Remove this!
55      public void assembleBuildInheritance(Build childBuild, Build parentBuild, boolean handleAsInheritance) {
56          // The build has been set but we want to step in here and fill in
57          // values that have not been set by the child.
58  
59          if (childBuild.getSourceDirectory() == null) {
60              childBuild.setSourceDirectory(parentBuild.getSourceDirectory());
61          }
62  
63          if (childBuild.getScriptSourceDirectory() == null) {
64              childBuild.setScriptSourceDirectory(parentBuild.getScriptSourceDirectory());
65          }
66  
67          if (childBuild.getTestSourceDirectory() == null) {
68              childBuild.setTestSourceDirectory(parentBuild.getTestSourceDirectory());
69          }
70  
71          if (childBuild.getOutputDirectory() == null) {
72              childBuild.setOutputDirectory(parentBuild.getOutputDirectory());
73          }
74  
75          if (childBuild.getTestOutputDirectory() == null) {
76              childBuild.setTestOutputDirectory(parentBuild.getTestOutputDirectory());
77          }
78  
79          // Extensions are accumulated
80          mergeExtensionLists(childBuild, parentBuild);
81  
82          if (childBuild.getDirectory() == null) {
83              childBuild.setDirectory(parentBuild.getDirectory());
84          }
85  
86          if (childBuild.getDefaultGoal() == null) {
87              childBuild.setDefaultGoal(parentBuild.getDefaultGoal());
88          }
89  
90          if (childBuild.getFinalName() == null) {
91              childBuild.setFinalName(parentBuild.getFinalName());
92          }
93  
94          ModelUtils.mergeFilterLists(childBuild.getFilters(), parentBuild.getFilters());
95  
96          List<Resource> resources = childBuild.getResources();
97          if ((resources == null) || resources.isEmpty()) {
98              childBuild.setResources(parentBuild.getResources());
99          }
100 
101         resources = childBuild.getTestResources();
102         if ((resources == null) || resources.isEmpty()) {
103             childBuild.setTestResources(parentBuild.getTestResources());
104         }
105 
106         // Plugins are aggregated if Plugin.inherit != false
107         ModelUtils.mergePluginLists(childBuild, parentBuild, handleAsInheritance);
108 
109         // Plugin management :: aggregate
110         PluginManagement dominantPM = childBuild.getPluginManagement();
111         PluginManagement recessivePM = parentBuild.getPluginManagement();
112 
113         if ((dominantPM == null) && (recessivePM != null)) {
114             // FIXME: Filter out the inherited == false stuff!
115             childBuild.setPluginManagement(recessivePM);
116         } else {
117             ModelUtils.mergePluginLists(childBuild.getPluginManagement(), parentBuild.getPluginManagement(), false);
118         }
119     }
120 
121     private void assembleScmInheritance(Model child, Model parent, String childPathAdjustment, boolean appendPaths) {
122         if (parent.getScm() != null) {
123             Scm parentScm = parent.getScm();
124 
125             Scm childScm = child.getScm();
126 
127             if (childScm == null) {
128                 childScm = new Scm();
129 
130                 child.setScm(childScm);
131             }
132 
133             if (StringUtils.isEmpty(childScm.getConnection()) && !StringUtils.isEmpty(parentScm.getConnection())) {
134                 childScm.setConnection(
135                         appendPath(parentScm.getConnection(), child.getArtifactId(), childPathAdjustment, appendPaths));
136             }
137 
138             if (StringUtils.isEmpty(childScm.getDeveloperConnection())
139                     && !StringUtils.isEmpty(parentScm.getDeveloperConnection())) {
140                 childScm.setDeveloperConnection(appendPath(
141                         parentScm.getDeveloperConnection(), child.getArtifactId(), childPathAdjustment, appendPaths));
142             }
143 
144             if (StringUtils.isEmpty(childScm.getUrl()) && !StringUtils.isEmpty(parentScm.getUrl())) {
145                 childScm.setUrl(
146                         appendPath(parentScm.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths));
147             }
148         }
149     }
150 
151     public void copyModel(Model dest, Model source) {
152         assembleModelInheritance(dest, source, null, false);
153     }
154 
155     public void assembleModelInheritance(Model child, Model parent, String childPathAdjustment) {
156         assembleModelInheritance(child, parent, childPathAdjustment, true);
157     }
158 
159     public void assembleModelInheritance(Model child, Model parent) {
160         assembleModelInheritance(child, parent, null, true);
161     }
162 
163     private void assembleModelInheritance(Model child, Model parent, String childPathAdjustment, boolean appendPaths) {
164         // cannot inherit from null parent.
165         if (parent == null) {
166             return;
167         }
168 
169         // Group id
170         if (child.getGroupId() == null) {
171             child.setGroupId(parent.getGroupId());
172         }
173 
174         // version
175         if (child.getVersion() == null) {
176             // The parent version may have resolved to something different, so we take what we asked for...
177             // instead of - child.setVersion( parent.getVersion() );
178 
179             if (child.getParent() != null) {
180                 child.setVersion(child.getParent().getVersion());
181             }
182         }
183 
184         // inceptionYear
185         if (child.getInceptionYear() == null) {
186             child.setInceptionYear(parent.getInceptionYear());
187         }
188 
189         // url
190         if (child.getUrl() == null) {
191             if (parent.getUrl() != null) {
192                 child.setUrl(appendPath(parent.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths));
193             } else {
194                 child.setUrl(parent.getUrl());
195             }
196         }
197 
198         assembleDistributionInheritance(child, parent, childPathAdjustment, appendPaths);
199 
200         // issueManagement
201         if (child.getIssueManagement() == null) {
202             child.setIssueManagement(parent.getIssueManagement());
203         }
204 
205         // description
206         if (child.getDescription() == null) {
207             child.setDescription(parent.getDescription());
208         }
209 
210         // Organization
211         if (child.getOrganization() == null) {
212             child.setOrganization(parent.getOrganization());
213         }
214 
215         // Scm
216         assembleScmInheritance(child, parent, childPathAdjustment, appendPaths);
217 
218         // ciManagement
219         if (child.getCiManagement() == null) {
220             child.setCiManagement(parent.getCiManagement());
221         }
222 
223         // developers
224         if (child.getDevelopers().size() == 0) {
225             child.setDevelopers(parent.getDevelopers());
226         }
227 
228         // licenses
229         if (child.getLicenses().size() == 0) {
230             child.setLicenses(parent.getLicenses());
231         }
232 
233         // developers
234         if (child.getContributors().size() == 0) {
235             child.setContributors(parent.getContributors());
236         }
237 
238         // mailingLists
239         if (child.getMailingLists().size() == 0) {
240             child.setMailingLists(parent.getMailingLists());
241         }
242 
243         // Build
244         assembleBuildInheritance(child, parent);
245 
246         assembleDependencyInheritance(child, parent);
247 
248         child.setRepositories(ModelUtils.mergeRepositoryLists(child.getRepositories(), parent.getRepositories()));
249         //        child.setPluginRepositories(
250         //            ModelUtils.mergeRepositoryLists( child.getPluginRepositories(), parent.getPluginRepositories() )
251         // );
252 
253         assembleReportingInheritance(child, parent);
254 
255         assembleDependencyManagementInheritance(child, parent);
256 
257         Properties props = new Properties();
258         props.putAll(parent.getProperties());
259         props.putAll(child.getProperties());
260 
261         child.setProperties(props);
262     }
263 
264     // TODO Remove this!
265     private void assembleDependencyManagementInheritance(Model child, Model parent) {
266         DependencyManagement parentDepMgmt = parent.getDependencyManagement();
267 
268         DependencyManagement childDepMgmt = child.getDependencyManagement();
269 
270         if (parentDepMgmt != null) {
271             if (childDepMgmt == null) {
272                 child.setDependencyManagement(parentDepMgmt);
273             } else {
274                 List<Dependency> childDeps = childDepMgmt.getDependencies();
275 
276                 Map<String, Dependency> mappedChildDeps = new TreeMap<>();
277                 for (Dependency dep : childDeps) {
278                     mappedChildDeps.put(dep.getManagementKey(), dep);
279                 }
280 
281                 for (Dependency dep : parentDepMgmt.getDependencies()) {
282                     if (!mappedChildDeps.containsKey(dep.getManagementKey())) {
283                         childDepMgmt.addDependency(dep);
284                     }
285                 }
286             }
287         }
288     }
289 
290     private void assembleReportingInheritance(Model child, Model parent) {
291         // Reports :: aggregate
292         Reporting childReporting = child.getReporting();
293         Reporting parentReporting = parent.getReporting();
294 
295         if (parentReporting != null) {
296             if (childReporting == null) {
297                 childReporting = new Reporting();
298                 child.setReporting(childReporting);
299             }
300 
301             childReporting.setExcludeDefaults(parentReporting.isExcludeDefaults());
302 
303             if (StringUtils.isEmpty(childReporting.getOutputDirectory())) {
304                 childReporting.setOutputDirectory(parentReporting.getOutputDirectory());
305             }
306 
307             mergeReportPluginLists(childReporting, parentReporting, true);
308         }
309     }
310 
311     private static void mergeReportPluginLists(Reporting child, Reporting parent, boolean handleAsInheritance) {
312         if ((child == null) || (parent == null)) {
313             // nothing to do.
314             return;
315         }
316 
317         List<ReportPlugin> parentPlugins = parent.getPlugins();
318 
319         if ((parentPlugins != null) && !parentPlugins.isEmpty()) {
320             Map<String, ReportPlugin> assembledPlugins = new TreeMap<>();
321 
322             Map<String, ReportPlugin> childPlugins = child.getReportPluginsAsMap();
323 
324             for (ReportPlugin parentPlugin : parentPlugins) {
325                 String parentInherited = parentPlugin.getInherited();
326 
327                 if (!handleAsInheritance || (parentInherited == null) || Boolean.parseBoolean(parentInherited)) {
328 
329                     ReportPlugin assembledPlugin = parentPlugin;
330 
331                     ReportPlugin childPlugin = childPlugins.get(parentPlugin.getKey());
332 
333                     if (childPlugin != null) {
334                         assembledPlugin = childPlugin;
335 
336                         mergeReportPluginDefinitions(childPlugin, parentPlugin, handleAsInheritance);
337                     }
338 
339                     if (handleAsInheritance && (parentInherited == null)) {
340                         assembledPlugin.unsetInheritanceApplied();
341                     }
342 
343                     assembledPlugins.put(assembledPlugin.getKey(), assembledPlugin);
344                 }
345             }
346 
347             for (ReportPlugin childPlugin : childPlugins.values()) {
348                 if (!assembledPlugins.containsKey(childPlugin.getKey())) {
349                     assembledPlugins.put(childPlugin.getKey(), childPlugin);
350                 }
351             }
352 
353             child.setPlugins(new ArrayList<>(assembledPlugins.values()));
354 
355             child.flushReportPluginMap();
356         }
357     }
358 
359     private static void mergeReportSetDefinitions(ReportSet child, ReportSet parent) {
360         List<String> parentReports = parent.getReports();
361         List<String> childReports = child.getReports();
362 
363         List<String> reports = new ArrayList<>();
364 
365         if ((childReports != null) && !childReports.isEmpty()) {
366             reports.addAll(childReports);
367         }
368 
369         if (parentReports != null) {
370             for (String report : parentReports) {
371                 if (!reports.contains(report)) {
372                     reports.add(report);
373                 }
374             }
375         }
376 
377         child.setReports(reports);
378 
379         Xpp3Dom childConfiguration = (Xpp3Dom) child.getConfiguration();
380         Xpp3Dom parentConfiguration = (Xpp3Dom) parent.getConfiguration();
381 
382         childConfiguration = Xpp3Dom.mergeXpp3Dom(childConfiguration, parentConfiguration);
383 
384         child.setConfiguration(childConfiguration);
385     }
386 
387     public static void mergeReportPluginDefinitions(
388             ReportPlugin child, ReportPlugin parent, boolean handleAsInheritance) {
389         if ((child == null) || (parent == null)) {
390             // nothing to do.
391             return;
392         }
393 
394         if ((child.getVersion() == null) && (parent.getVersion() != null)) {
395             child.setVersion(parent.getVersion());
396         }
397 
398         // from here to the end of the method is dealing with merging of the <executions/> section.
399         String parentInherited = parent.getInherited();
400 
401         boolean parentIsInherited = (parentInherited == null) || Boolean.parseBoolean(parentInherited);
402 
403         List<ReportSet> parentReportSets = parent.getReportSets();
404 
405         if ((parentReportSets != null) && !parentReportSets.isEmpty()) {
406             Map<String, ReportSet> assembledReportSets = new TreeMap<>();
407 
408             Map<String, ReportSet> childReportSets = child.getReportSetsAsMap();
409 
410             for (Object parentReportSet1 : parentReportSets) {
411                 ReportSet parentReportSet = (ReportSet) parentReportSet1;
412 
413                 if (!handleAsInheritance || parentIsInherited) {
414                     ReportSet assembledReportSet = parentReportSet;
415 
416                     ReportSet childReportSet = childReportSets.get(parentReportSet.getId());
417 
418                     if (childReportSet != null) {
419                         mergeReportSetDefinitions(childReportSet, parentReportSet);
420 
421                         assembledReportSet = childReportSet;
422                     } else if (handleAsInheritance && (parentInherited == null)) {
423                         parentReportSet.unsetInheritanceApplied();
424                     }
425 
426                     assembledReportSets.put(assembledReportSet.getId(), assembledReportSet);
427                 }
428             }
429 
430             for (Map.Entry<String, ReportSet> entry : childReportSets.entrySet()) {
431                 String id = entry.getKey();
432 
433                 if (!assembledReportSets.containsKey(id)) {
434                     assembledReportSets.put(id, entry.getValue());
435                 }
436             }
437 
438             child.setReportSets(new ArrayList<>(assembledReportSets.values()));
439 
440             child.flushReportSetMap();
441         }
442     }
443 
444     // TODO Remove this!
445     private void assembleDependencyInheritance(Model child, Model parent) {
446         Map<String, Dependency> depsMap = new LinkedHashMap<>();
447 
448         List<Dependency> deps = parent.getDependencies();
449 
450         if (deps != null) {
451             for (Dependency dependency : deps) {
452                 depsMap.put(dependency.getManagementKey(), dependency);
453             }
454         }
455 
456         deps = child.getDependencies();
457 
458         if (deps != null) {
459             for (Dependency dependency : deps) {
460                 depsMap.put(dependency.getManagementKey(), dependency);
461             }
462         }
463 
464         child.setDependencies(new ArrayList<>(depsMap.values()));
465     }
466 
467     private void assembleBuildInheritance(Model child, Model parent) {
468         Build childBuild = child.getBuild();
469         Build parentBuild = parent.getBuild();
470 
471         if (parentBuild != null) {
472             if (childBuild == null) {
473                 childBuild = new Build();
474                 child.setBuild(childBuild);
475             }
476 
477             assembleBuildInheritance(childBuild, parentBuild, true);
478         }
479     }
480 
481     private void assembleDistributionInheritance(
482             Model child, Model parent, String childPathAdjustment, boolean appendPaths) {
483         if (parent.getDistributionManagement() != null) {
484             DistributionManagement parentDistMgmt = parent.getDistributionManagement();
485 
486             DistributionManagement childDistMgmt = child.getDistributionManagement();
487 
488             if (childDistMgmt == null) {
489                 childDistMgmt = new DistributionManagement();
490 
491                 child.setDistributionManagement(childDistMgmt);
492             }
493 
494             if (childDistMgmt.getSite() == null) {
495                 if (parentDistMgmt.getSite() != null) {
496                     Site site = new Site();
497 
498                     childDistMgmt.setSite(site);
499 
500                     site.setId(parentDistMgmt.getSite().getId());
501 
502                     site.setName(parentDistMgmt.getSite().getName());
503 
504                     site.setUrl(parentDistMgmt.getSite().getUrl());
505 
506                     if (site.getUrl() != null) {
507                         site.setUrl(appendPath(site.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths));
508                     }
509                 }
510             }
511 
512             if (childDistMgmt.getRepository() == null) {
513                 if (parentDistMgmt.getRepository() != null) {
514                     DeploymentRepository repository = copyDistributionRepository(parentDistMgmt.getRepository());
515                     childDistMgmt.setRepository(repository);
516                 }
517             }
518 
519             if (childDistMgmt.getSnapshotRepository() == null) {
520                 if (parentDistMgmt.getSnapshotRepository() != null) {
521                     DeploymentRepository repository =
522                             copyDistributionRepository(parentDistMgmt.getSnapshotRepository());
523                     childDistMgmt.setSnapshotRepository(repository);
524                 }
525             }
526 
527             if (StringUtils.isEmpty(childDistMgmt.getDownloadUrl())) {
528                 childDistMgmt.setDownloadUrl(parentDistMgmt.getDownloadUrl());
529             }
530 
531             // NOTE: We SHOULD NOT be inheriting status, since this is an assessment of the POM quality.
532             // NOTE: We SHOULD NOT be inheriting relocation, since this relates to a single POM
533         }
534     }
535 
536     private static DeploymentRepository copyDistributionRepository(DeploymentRepository parentRepository) {
537         DeploymentRepository repository = new DeploymentRepository();
538 
539         repository.setId(parentRepository.getId());
540 
541         repository.setName(parentRepository.getName());
542 
543         repository.setUrl(parentRepository.getUrl());
544 
545         repository.setLayout(parentRepository.getLayout());
546 
547         repository.setUniqueVersion(parentRepository.isUniqueVersion());
548 
549         return repository;
550     }
551 
552     // TODO This should eventually be migrated to DefaultPathTranslator.
553     protected String appendPath(String parentPath, String childPath, String pathAdjustment, boolean appendPaths) {
554         String uncleanPath = parentPath;
555 
556         if (appendPaths) {
557             if (pathAdjustment != null) {
558                 uncleanPath += "/" + pathAdjustment;
559             }
560 
561             if (childPath != null) {
562                 uncleanPath += "/" + childPath;
563             }
564         }
565 
566         String cleanedPath = "";
567 
568         int protocolIdx = uncleanPath.indexOf("://");
569 
570         if (protocolIdx > -1) {
571             cleanedPath = uncleanPath.substring(0, protocolIdx + 3);
572             uncleanPath = uncleanPath.substring(protocolIdx + 3);
573         }
574 
575         if (uncleanPath.startsWith("/")) {
576             cleanedPath += "/";
577         }
578 
579         return cleanedPath + resolvePath(uncleanPath);
580     }
581 
582     // TODO Move this to plexus-utils' PathTool.
583     private static String resolvePath(String uncleanPath) {
584         LinkedList<String> pathElements = new LinkedList<>();
585 
586         StringTokenizer tokenizer = new StringTokenizer(uncleanPath, "/");
587 
588         while (tokenizer.hasMoreTokens()) {
589             String token = tokenizer.nextToken();
590 
591             switch (token) {
592                 case "":
593                     // Empty path entry ("...//.."), remove.
594                     break;
595                 case "..":
596                     if (pathElements.isEmpty()) {
597                         // FIXME: somehow report to the user
598                         // that there are too many '..' elements.
599                         // For now, ignore the extra '..'.
600                     } else {
601                         pathElements.removeLast();
602                     }
603                     break;
604                 default:
605                     pathElements.addLast(token);
606                     break;
607             }
608         }
609 
610         StringBuilder cleanedPath = new StringBuilder();
611 
612         while (!pathElements.isEmpty()) {
613             cleanedPath.append(pathElements.removeFirst());
614             if (!pathElements.isEmpty()) {
615                 cleanedPath.append('/');
616             }
617         }
618 
619         return cleanedPath.toString();
620     }
621 
622     private static void mergeExtensionLists(Build childBuild, Build parentBuild) {
623         for (Extension e : parentBuild.getExtensions()) {
624             if (!childBuild.getExtensions().contains(e)) {
625                 childBuild.addExtension(e);
626             }
627         }
628     }
629 }