001package org.apache.maven.project.inheritance; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.util.ArrayList; 023import java.util.LinkedHashMap; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Properties; 028import java.util.StringTokenizer; 029import java.util.TreeMap; 030 031import org.apache.maven.model.Build; 032import org.apache.maven.model.Dependency; 033import org.apache.maven.model.DependencyManagement; 034import org.apache.maven.model.DeploymentRepository; 035import org.apache.maven.model.DistributionManagement; 036import org.apache.maven.model.Extension; 037import org.apache.maven.model.Model; 038import org.apache.maven.model.PluginManagement; 039import org.apache.maven.model.ReportPlugin; 040import org.apache.maven.model.ReportSet; 041import org.apache.maven.model.Reporting; 042import org.apache.maven.model.Resource; 043import org.apache.maven.model.Scm; 044import org.apache.maven.model.Site; 045import org.apache.maven.project.ModelUtils; 046import org.codehaus.plexus.component.annotations.Component; 047import org.codehaus.plexus.util.StringUtils; 048import org.codehaus.plexus.util.xml.Xpp3Dom; 049 050@Component( role = ModelInheritanceAssembler.class ) 051public class DefaultModelInheritanceAssembler 052 implements ModelInheritanceAssembler 053{ 054 // TODO: Remove this! 055 @SuppressWarnings( "unchecked" ) 056 public void assembleBuildInheritance( Build childBuild, Build parentBuild, boolean handleAsInheritance ) 057 { 058 // The build has been set but we want to step in here and fill in 059 // values that have not been set by the child. 060 061 if ( childBuild.getSourceDirectory() == null ) 062 { 063 childBuild.setSourceDirectory( parentBuild.getSourceDirectory() ); 064 } 065 066 if ( childBuild.getScriptSourceDirectory() == null ) 067 { 068 childBuild.setScriptSourceDirectory( parentBuild.getScriptSourceDirectory() ); 069 } 070 071 if ( childBuild.getTestSourceDirectory() == null ) 072 { 073 childBuild.setTestSourceDirectory( parentBuild.getTestSourceDirectory() ); 074 } 075 076 if ( childBuild.getOutputDirectory() == null ) 077 { 078 childBuild.setOutputDirectory( parentBuild.getOutputDirectory() ); 079 } 080 081 if ( childBuild.getTestOutputDirectory() == null ) 082 { 083 childBuild.setTestOutputDirectory( parentBuild.getTestOutputDirectory() ); 084 } 085 086 // Extensions are accumulated 087 mergeExtensionLists( childBuild, parentBuild ); 088 089 if ( childBuild.getDirectory() == null ) 090 { 091 childBuild.setDirectory( parentBuild.getDirectory() ); 092 } 093 094 if ( childBuild.getDefaultGoal() == null ) 095 { 096 childBuild.setDefaultGoal( parentBuild.getDefaultGoal() ); 097 } 098 099 if ( childBuild.getFinalName() == null ) 100 { 101 childBuild.setFinalName( parentBuild.getFinalName() ); 102 } 103 104 ModelUtils.mergeFilterLists( childBuild.getFilters(), parentBuild.getFilters() ); 105 106 List<Resource> resources = childBuild.getResources(); 107 if ( ( resources == null ) || resources.isEmpty() ) 108 { 109 childBuild.setResources( parentBuild.getResources() ); 110 } 111 112 resources = childBuild.getTestResources(); 113 if ( ( resources == null ) || resources.isEmpty() ) 114 { 115 childBuild.setTestResources( parentBuild.getTestResources() ); 116 } 117 118 // Plugins are aggregated if Plugin.inherit != false 119 ModelUtils.mergePluginLists( childBuild, parentBuild, handleAsInheritance ); 120 121 // Plugin management :: aggregate 122 PluginManagement dominantPM = childBuild.getPluginManagement(); 123 PluginManagement recessivePM = parentBuild.getPluginManagement(); 124 125 if ( ( dominantPM == null ) && ( recessivePM != null ) ) 126 { 127 // FIXME: Filter out the inherited == false stuff! 128 childBuild.setPluginManagement( recessivePM ); 129 } 130 else 131 { 132 ModelUtils.mergePluginLists( childBuild.getPluginManagement(), parentBuild.getPluginManagement(), false ); 133 } 134 } 135 136 private void assembleScmInheritance( Model child, Model parent, String childPathAdjustment, boolean appendPaths ) 137 { 138 if ( parent.getScm() != null ) 139 { 140 Scm parentScm = parent.getScm(); 141 142 Scm childScm = child.getScm(); 143 144 if ( childScm == null ) 145 { 146 childScm = new Scm(); 147 148 child.setScm( childScm ); 149 } 150 151 if ( StringUtils.isEmpty( childScm.getConnection() ) && !StringUtils.isEmpty( parentScm.getConnection() ) ) 152 { 153 childScm.setConnection( 154 appendPath( parentScm.getConnection(), child.getArtifactId(), childPathAdjustment, appendPaths ) ); 155 } 156 157 if ( StringUtils.isEmpty( childScm.getDeveloperConnection() ) 158 && !StringUtils.isEmpty( parentScm.getDeveloperConnection() ) ) 159 { 160 childScm 161 .setDeveloperConnection( appendPath( parentScm.getDeveloperConnection(), child.getArtifactId(), 162 childPathAdjustment, appendPaths ) ); 163 } 164 165 if ( StringUtils.isEmpty( childScm.getUrl() ) && !StringUtils.isEmpty( parentScm.getUrl() ) ) 166 { 167 childScm.setUrl( 168 appendPath( parentScm.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) ); 169 } 170 } 171 } 172 173 public void copyModel( Model dest, Model source ) 174 { 175 assembleModelInheritance( dest, source, null, false ); 176 } 177 178 public void assembleModelInheritance( Model child, Model parent, String childPathAdjustment ) 179 { 180 assembleModelInheritance( child, parent, childPathAdjustment, true ); 181 } 182 183 public void assembleModelInheritance( Model child, Model parent ) 184 { 185 assembleModelInheritance( child, parent, null, true ); 186 } 187 188 private void assembleModelInheritance( Model child, Model parent, String childPathAdjustment, boolean appendPaths ) 189 { 190 // cannot inherit from null parent. 191 if ( parent == null ) 192 { 193 return; 194 } 195 196 // Group id 197 if ( child.getGroupId() == null ) 198 { 199 child.setGroupId( parent.getGroupId() ); 200 } 201 202 // version 203 if ( child.getVersion() == null ) 204 { 205 // The parent version may have resolved to something different, so we take what we asked for... 206 // instead of - child.setVersion( parent.getVersion() ); 207 208 if ( child.getParent() != null ) 209 { 210 child.setVersion( child.getParent().getVersion() ); 211 } 212 } 213 214 // inceptionYear 215 if ( child.getInceptionYear() == null ) 216 { 217 child.setInceptionYear( parent.getInceptionYear() ); 218 } 219 220 // url 221 if ( child.getUrl() == null ) 222 { 223 if ( parent.getUrl() != null ) 224 { 225 child.setUrl( appendPath( parent.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) ); 226 } 227 else 228 { 229 child.setUrl( parent.getUrl() ); 230 } 231 } 232 233 assembleDistributionInheritence( child, parent, childPathAdjustment, appendPaths ); 234 235 // issueManagement 236 if ( child.getIssueManagement() == null ) 237 { 238 child.setIssueManagement( parent.getIssueManagement() ); 239 } 240 241 // description 242 if ( child.getDescription() == null ) 243 { 244 child.setDescription( parent.getDescription() ); 245 } 246 247 // Organization 248 if ( child.getOrganization() == null ) 249 { 250 child.setOrganization( parent.getOrganization() ); 251 } 252 253 // Scm 254 assembleScmInheritance( child, parent, childPathAdjustment, appendPaths ); 255 256 // ciManagement 257 if ( child.getCiManagement() == null ) 258 { 259 child.setCiManagement( parent.getCiManagement() ); 260 } 261 262 // developers 263 if ( child.getDevelopers().size() == 0 ) 264 { 265 child.setDevelopers( parent.getDevelopers() ); 266 } 267 268 // licenses 269 if ( child.getLicenses().size() == 0 ) 270 { 271 child.setLicenses( parent.getLicenses() ); 272 } 273 274 // developers 275 if ( child.getContributors().size() == 0 ) 276 { 277 child.setContributors( parent.getContributors() ); 278 } 279 280 // mailingLists 281 if ( child.getMailingLists().size() == 0 ) 282 { 283 child.setMailingLists( parent.getMailingLists() ); 284 } 285 286 // Build 287 assembleBuildInheritance( child, parent ); 288 289 assembleDependencyInheritance( child, parent ); 290 291 child.setRepositories( ModelUtils.mergeRepositoryLists( child.getRepositories(), parent.getRepositories() ) ); 292// child.setPluginRepositories( 293// ModelUtils.mergeRepositoryLists( child.getPluginRepositories(), parent.getPluginRepositories() ) ); 294 295 assembleReportingInheritance( child, parent ); 296 297 assembleDependencyManagementInheritance( child, parent ); 298 299 Properties props = new Properties(); 300 props.putAll( parent.getProperties() ); 301 props.putAll( child.getProperties() ); 302 303 child.setProperties( props ); 304 } 305 306 // TODO: Remove this! 307 @SuppressWarnings( "unchecked" ) 308 private void assembleDependencyManagementInheritance( Model child, Model parent ) 309 { 310 DependencyManagement parentDepMgmt = parent.getDependencyManagement(); 311 312 DependencyManagement childDepMgmt = child.getDependencyManagement(); 313 314 if ( parentDepMgmt != null ) 315 { 316 if ( childDepMgmt == null ) 317 { 318 child.setDependencyManagement( parentDepMgmt ); 319 } 320 else 321 { 322 List<Dependency> childDeps = childDepMgmt.getDependencies(); 323 324 Map<String, Dependency> mappedChildDeps = new TreeMap<>(); 325 for ( Dependency dep : childDeps ) 326 { 327 mappedChildDeps.put( dep.getManagementKey(), dep ); 328 } 329 330 for ( Dependency dep : parentDepMgmt.getDependencies() ) 331 { 332 if ( !mappedChildDeps.containsKey( dep.getManagementKey() ) ) 333 { 334 childDepMgmt.addDependency( dep ); 335 } 336 } 337 } 338 } 339 } 340 341 private void assembleReportingInheritance( Model child, Model parent ) 342 { 343 // Reports :: aggregate 344 Reporting childReporting = child.getReporting(); 345 Reporting parentReporting = parent.getReporting(); 346 347 if ( parentReporting != null ) 348 { 349 if ( childReporting == null ) 350 { 351 childReporting = new Reporting(); 352 child.setReporting( childReporting ); 353 } 354 355 childReporting.setExcludeDefaults( parentReporting.isExcludeDefaults() ); 356 357 if ( StringUtils.isEmpty( childReporting.getOutputDirectory() ) ) 358 { 359 childReporting.setOutputDirectory( parentReporting.getOutputDirectory() ); 360 } 361 362 mergeReportPluginLists( childReporting, parentReporting, true ); 363 } 364 } 365 366 private static void mergeReportPluginLists( Reporting child, Reporting parent, boolean handleAsInheritance ) 367 { 368 if ( ( child == null ) || ( parent == null ) ) 369 { 370 // nothing to do. 371 return; 372 } 373 374 List<ReportPlugin> parentPlugins = parent.getPlugins(); 375 376 if ( ( parentPlugins != null ) && !parentPlugins.isEmpty() ) 377 { 378 Map<String, ReportPlugin> assembledPlugins = new TreeMap<>(); 379 380 Map<String, ReportPlugin> childPlugins = child.getReportPluginsAsMap(); 381 382 for ( ReportPlugin parentPlugin : parentPlugins ) 383 { 384 String parentInherited = parentPlugin.getInherited(); 385 386 if ( !handleAsInheritance || ( parentInherited == null ) || Boolean.valueOf( parentInherited ) ) 387 { 388 389 ReportPlugin assembledPlugin = parentPlugin; 390 391 ReportPlugin childPlugin = childPlugins.get( parentPlugin.getKey() ); 392 393 if ( childPlugin != null ) 394 { 395 assembledPlugin = childPlugin; 396 397 mergeReportPluginDefinitions( childPlugin, parentPlugin, handleAsInheritance ); 398 } 399 400 if ( handleAsInheritance && ( parentInherited == null ) ) 401 { 402 assembledPlugin.unsetInheritanceApplied(); 403 } 404 405 assembledPlugins.put( assembledPlugin.getKey(), assembledPlugin ); 406 } 407 } 408 409 for ( ReportPlugin childPlugin : childPlugins.values() ) 410 { 411 if ( !assembledPlugins.containsKey( childPlugin.getKey() ) ) 412 { 413 assembledPlugins.put( childPlugin.getKey(), childPlugin ); 414 } 415 } 416 417 child.setPlugins( new ArrayList<>( assembledPlugins.values() ) ); 418 419 child.flushReportPluginMap(); 420 } 421 } 422 423 private static void mergeReportSetDefinitions( ReportSet child, ReportSet parent ) 424 { 425 List<String> parentReports = parent.getReports(); 426 List<String> childReports = child.getReports(); 427 428 List<String> reports = new ArrayList<>(); 429 430 if ( ( childReports != null ) && !childReports.isEmpty() ) 431 { 432 reports.addAll( childReports ); 433 } 434 435 if ( parentReports != null ) 436 { 437 for ( String report : parentReports ) 438 { 439 if ( !reports.contains( report ) ) 440 { 441 reports.add( report ); 442 } 443 } 444 } 445 446 child.setReports( reports ); 447 448 Xpp3Dom childConfiguration = (Xpp3Dom) child.getConfiguration(); 449 Xpp3Dom parentConfiguration = (Xpp3Dom) parent.getConfiguration(); 450 451 childConfiguration = Xpp3Dom.mergeXpp3Dom( childConfiguration, parentConfiguration ); 452 453 child.setConfiguration( childConfiguration ); 454 } 455 456 457 public static void mergeReportPluginDefinitions( ReportPlugin child, ReportPlugin parent, 458 boolean handleAsInheritance ) 459 { 460 if ( ( child == null ) || ( parent == null ) ) 461 { 462 // nothing to do. 463 return; 464 } 465 466 if ( ( child.getVersion() == null ) && ( parent.getVersion() != null ) ) 467 { 468 child.setVersion( parent.getVersion() ); 469 } 470 471 // from here to the end of the method is dealing with merging of the <executions/> section. 472 String parentInherited = parent.getInherited(); 473 474 boolean parentIsInherited = ( parentInherited == null ) || Boolean.valueOf( parentInherited ); 475 476 List<ReportSet> parentReportSets = parent.getReportSets(); 477 478 if ( ( parentReportSets != null ) && !parentReportSets.isEmpty() ) 479 { 480 Map<String, ReportSet> assembledReportSets = new TreeMap<>(); 481 482 Map<String, ReportSet> childReportSets = child.getReportSetsAsMap(); 483 484 for ( Object parentReportSet1 : parentReportSets ) 485 { 486 ReportSet parentReportSet = (ReportSet) parentReportSet1; 487 488 if ( !handleAsInheritance || parentIsInherited ) 489 { 490 ReportSet assembledReportSet = parentReportSet; 491 492 ReportSet childReportSet = childReportSets.get( parentReportSet.getId() ); 493 494 if ( childReportSet != null ) 495 { 496 mergeReportSetDefinitions( childReportSet, parentReportSet ); 497 498 assembledReportSet = childReportSet; 499 } 500 else if ( handleAsInheritance && ( parentInherited == null ) ) 501 { 502 parentReportSet.unsetInheritanceApplied(); 503 } 504 505 assembledReportSets.put( assembledReportSet.getId(), assembledReportSet ); 506 } 507 } 508 509 for ( Map.Entry<String, ReportSet> entry : childReportSets.entrySet() ) 510 { 511 String id = entry.getKey(); 512 513 if ( !assembledReportSets.containsKey( id ) ) 514 { 515 assembledReportSets.put( id, entry.getValue() ); 516 } 517 } 518 519 child.setReportSets( new ArrayList<>( assembledReportSets.values() ) ); 520 521 child.flushReportSetMap(); 522 } 523 524 } 525 526 // TODO: Remove this! 527 @SuppressWarnings( "unchecked" ) 528 private void assembleDependencyInheritance( Model child, Model parent ) 529 { 530 Map<String, Dependency> depsMap = new LinkedHashMap<>(); 531 532 List<Dependency> deps = parent.getDependencies(); 533 534 if ( deps != null ) 535 { 536 for ( Dependency dependency : deps ) 537 { 538 depsMap.put( dependency.getManagementKey(), dependency ); 539 } 540 } 541 542 deps = child.getDependencies(); 543 544 if ( deps != null ) 545 { 546 for ( Dependency dependency : deps ) 547 { 548 depsMap.put( dependency.getManagementKey(), dependency ); 549 } 550 } 551 552 child.setDependencies( new ArrayList<>( depsMap.values() ) ); 553 } 554 555 private void assembleBuildInheritance( Model child, Model parent ) 556 { 557 Build childBuild = child.getBuild(); 558 Build parentBuild = parent.getBuild(); 559 560 if ( parentBuild != null ) 561 { 562 if ( childBuild == null ) 563 { 564 childBuild = new Build(); 565 child.setBuild( childBuild ); 566 } 567 568 assembleBuildInheritance( childBuild, parentBuild, true ); 569 } 570 } 571 572 private void assembleDistributionInheritence( Model child, Model parent, String childPathAdjustment, 573 boolean appendPaths ) 574 { 575 if ( parent.getDistributionManagement() != null ) 576 { 577 DistributionManagement parentDistMgmt = parent.getDistributionManagement(); 578 579 DistributionManagement childDistMgmt = child.getDistributionManagement(); 580 581 if ( childDistMgmt == null ) 582 { 583 childDistMgmt = new DistributionManagement(); 584 585 child.setDistributionManagement( childDistMgmt ); 586 } 587 588 if ( childDistMgmt.getSite() == null ) 589 { 590 if ( parentDistMgmt.getSite() != null ) 591 { 592 Site site = new Site(); 593 594 childDistMgmt.setSite( site ); 595 596 site.setId( parentDistMgmt.getSite().getId() ); 597 598 site.setName( parentDistMgmt.getSite().getName() ); 599 600 site.setUrl( parentDistMgmt.getSite().getUrl() ); 601 602 if ( site.getUrl() != null ) 603 { 604 site.setUrl( 605 appendPath( site.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) ); 606 } 607 } 608 } 609 610 if ( childDistMgmt.getRepository() == null ) 611 { 612 if ( parentDistMgmt.getRepository() != null ) 613 { 614 DeploymentRepository repository = copyDistributionRepository( parentDistMgmt.getRepository() ); 615 childDistMgmt.setRepository( repository ); 616 } 617 } 618 619 if ( childDistMgmt.getSnapshotRepository() == null ) 620 { 621 if ( parentDistMgmt.getSnapshotRepository() != null ) 622 { 623 DeploymentRepository repository = 624 copyDistributionRepository( parentDistMgmt.getSnapshotRepository() ); 625 childDistMgmt.setSnapshotRepository( repository ); 626 } 627 } 628 629 if ( StringUtils.isEmpty( childDistMgmt.getDownloadUrl() ) ) 630 { 631 childDistMgmt.setDownloadUrl( parentDistMgmt.getDownloadUrl() ); 632 } 633 634 // NOTE: We SHOULD NOT be inheriting status, since this is an assessment of the POM quality. 635 // NOTE: We SHOULD NOT be inheriting relocation, since this relates to a single POM 636 } 637 } 638 639 private static DeploymentRepository copyDistributionRepository( DeploymentRepository parentRepository ) 640 { 641 DeploymentRepository repository = new DeploymentRepository(); 642 643 repository.setId( parentRepository.getId() ); 644 645 repository.setName( parentRepository.getName() ); 646 647 repository.setUrl( parentRepository.getUrl() ); 648 649 repository.setLayout( parentRepository.getLayout() ); 650 651 repository.setUniqueVersion( parentRepository.isUniqueVersion() ); 652 653 return repository; 654 } 655 656 // TODO: This should eventually be migrated to DefaultPathTranslator. 657 protected String appendPath( String parentPath, String childPath, String pathAdjustment, boolean appendPaths ) 658 { 659 String uncleanPath = parentPath; 660 661 if ( appendPaths ) 662 { 663 if ( pathAdjustment != null ) 664 { 665 uncleanPath += "/" + pathAdjustment; 666 } 667 668 if ( childPath != null ) 669 { 670 uncleanPath += "/" + childPath; 671 } 672 } 673 674 String cleanedPath = ""; 675 676 int protocolIdx = uncleanPath.indexOf( "://" ); 677 678 if ( protocolIdx > -1 ) 679 { 680 cleanedPath = uncleanPath.substring( 0, protocolIdx + 3 ); 681 uncleanPath = uncleanPath.substring( protocolIdx + 3 ); 682 } 683 684 if ( uncleanPath.startsWith( "/" ) ) 685 { 686 cleanedPath += "/"; 687 } 688 689 return cleanedPath + resolvePath( uncleanPath ); 690 } 691 692 // TODO: Move this to plexus-utils' PathTool. 693 private static String resolvePath( String uncleanPath ) 694 { 695 LinkedList<String> pathElements = new LinkedList<>(); 696 697 StringTokenizer tokenizer = new StringTokenizer( uncleanPath, "/" ); 698 699 while ( tokenizer.hasMoreTokens() ) 700 { 701 String token = tokenizer.nextToken(); 702 703 switch ( token ) 704 { 705 case "": 706 // Empty path entry ("...//.."), remove. 707 break; 708 case "..": 709 if ( pathElements.isEmpty() ) 710 { 711 // FIXME: somehow report to the user 712 // that there are too many '..' elements. 713 // For now, ignore the extra '..'. 714 } 715 else 716 { 717 pathElements.removeLast(); 718 } 719 break; 720 default: 721 pathElements.addLast( token ); 722 break; 723 } 724 } 725 726 StringBuilder cleanedPath = new StringBuilder(); 727 728 while ( !pathElements.isEmpty() ) 729 { 730 cleanedPath.append( pathElements.removeFirst() ); 731 if ( !pathElements.isEmpty() ) 732 { 733 cleanedPath.append( '/' ); 734 } 735 } 736 737 return cleanedPath.toString(); 738 } 739 740 private static void mergeExtensionLists( Build childBuild, Build parentBuild ) 741 { 742 for ( Extension e : parentBuild.getExtensions() ) 743 { 744 if ( !childBuild.getExtensions().contains( e ) ) 745 { 746 childBuild.addExtension( e ); 747 } 748 } 749 } 750}