View Javadoc
1   package org.apache.maven.plugin.resources.remote;
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.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.FileOutputStream;
27  import java.io.FileReader;
28  import java.io.FileWriter;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.InputStreamReader;
32  import java.io.OutputStream;
33  import java.io.OutputStreamWriter;
34  import java.io.PrintWriter;
35  import java.io.Reader;
36  import java.io.StringReader;
37  import java.io.UnsupportedEncodingException;
38  import java.io.Writer;
39  import java.net.MalformedURLException;
40  import java.net.URL;
41  import java.text.SimpleDateFormat;
42  import java.util.AbstractMap;
43  import java.util.ArrayList;
44  import java.util.Arrays;
45  import java.util.Collections;
46  import java.util.Comparator;
47  import java.util.Date;
48  import java.util.Enumeration;
49  import java.util.HashMap;
50  import java.util.LinkedHashSet;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Properties;
54  import java.util.Set;
55  import java.util.TreeMap;
56  
57  import org.apache.commons.io.output.DeferredFileOutputStream;
58  import org.apache.maven.ProjectDependenciesResolver;
59  import org.apache.maven.archiver.MavenArchiver;
60  import org.apache.maven.artifact.Artifact;
61  import org.apache.maven.artifact.DefaultArtifact;
62  import org.apache.maven.artifact.repository.ArtifactRepository;
63  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
64  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
65  import org.apache.maven.artifact.versioning.VersionRange;
66  import org.apache.maven.execution.MavenSession;
67  import org.apache.maven.lifecycle.internal.ProjectArtifactFactory;
68  import org.apache.maven.model.Model;
69  import org.apache.maven.model.Organization;
70  import org.apache.maven.model.Resource;
71  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
72  import org.apache.maven.plugin.AbstractMojo;
73  import org.apache.maven.plugin.MojoExecutionException;
74  import org.apache.maven.plugin.resources.remote.io.xpp3.RemoteResourcesBundleXpp3Reader;
75  import org.apache.maven.plugin.resources.remote.io.xpp3.SupplementalDataModelXpp3Reader;
76  import org.apache.maven.plugins.annotations.Component;
77  import org.apache.maven.plugins.annotations.LifecyclePhase;
78  import org.apache.maven.plugins.annotations.Mojo;
79  import org.apache.maven.plugins.annotations.Parameter;
80  import org.apache.maven.project.DefaultProjectBuildingRequest;
81  import org.apache.maven.project.MavenProject;
82  import org.apache.maven.project.ProjectBuilder;
83  import org.apache.maven.project.ProjectBuildingException;
84  import org.apache.maven.project.ProjectBuildingRequest;
85  import org.apache.maven.project.ProjectBuildingResult;
86  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
87  import org.apache.maven.repository.RepositorySystem;
88  import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
89  import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter;
90  import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
91  import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
92  import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
93  import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
94  import org.apache.maven.shared.filtering.MavenFileFilter;
95  import org.apache.maven.shared.filtering.MavenFileFilterRequest;
96  import org.apache.maven.shared.filtering.MavenFilteringException;
97  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
98  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
99  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
100 import org.apache.velocity.VelocityContext;
101 import org.apache.velocity.app.Velocity;
102 import org.apache.velocity.app.VelocityEngine;
103 import org.apache.velocity.exception.MethodInvocationException;
104 import org.apache.velocity.exception.ParseErrorException;
105 import org.apache.velocity.exception.ResourceNotFoundException;
106 import org.apache.velocity.exception.VelocityException;
107 import org.apache.velocity.runtime.RuntimeConstants;
108 import org.apache.velocity.runtime.RuntimeServices;
109 import org.apache.velocity.runtime.log.LogChute;
110 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
111 import org.codehaus.plexus.resource.ResourceManager;
112 import org.codehaus.plexus.resource.loader.FileResourceLoader;
113 import org.codehaus.plexus.util.FileUtils;
114 import org.codehaus.plexus.util.IOUtil;
115 import org.codehaus.plexus.util.ReaderFactory;
116 import org.codehaus.plexus.util.StringUtils;
117 import org.codehaus.plexus.util.WriterFactory;
118 import org.codehaus.plexus.util.xml.Xpp3Dom;
119 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
120 
121 /**
122  * <p>
123  * Pull down resourceBundles containing remote resources and process the resources contained inside. When that is done,
124  * the resources are injected into the current (in-memory) Maven project, making them available to the process-resources
125  * phase.
126  * </p>
127  * <p>
128  * Resources that end in ".vm" are treated as Velocity templates. For those, the ".vm" is stripped off for the final
129  * artifact name and it's fed through Velocity to have properties expanded, conditions processed, etc...
130  * </p>
131  * Resources that don't end in ".vm" are copied "as is".
132  */
133 // NOTE: Removed the following in favor of maven-artifact-resolver library, for MRRESOURCES-41
134 // If I leave this intact, interdependent projects within the reactor that haven't been built
135 // (remember, this runs in the generate-resources phase) will cause the build to fail.
136 //
137 // @requiresDependencyResolution test
138 @Mojo( name = "process", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true )
139 public class ProcessRemoteResourcesMojo
140     extends AbstractMojo
141     implements LogChute
142 {
143 
144     private static final String TEMPLATE_SUFFIX = ".vm";
145 
146     /**
147      * <p>
148      * In cases where a local resource overrides one from a remote resource bundle, that resource should be filtered if
149      * the resource set specifies it. In those cases, this parameter defines the list of delimiters for filterable
150      * expressions. These delimiters are specified in the form 'beginToken*endToken'. If no '*' is given, the delimiter
151      * is assumed to be the same for start and end.
152      * </p>
153      * <p>
154      * So, the default filtering delimiters might be specified as:
155      * </p>
156      * 
157      * <pre>
158      * &lt;delimiters&gt;
159      *   &lt;delimiter&gt;${*}&lt;/delimiter&gt;
160      *   &lt;delimiter&gt;@&lt;/delimiter&gt;
161      * &lt;/delimiters&gt;
162      * </pre>
163      * Since the '@' delimiter is the same on both ends, we don't need to specify '@*@' (though we can).
164      *
165      * @since 1.1
166      */
167     @Parameter
168     protected List<String> filterDelimiters;
169 
170     /**
171      * @since 1.1
172      */
173     @Parameter( defaultValue = "true" )
174     protected boolean useDefaultFilterDelimiters;
175 
176     /**
177      * If true, only generate resources in the directory of the root project in a multimodule build.
178      * Dependencies from all modules will be aggregated before resource-generation takes place.
179      *
180      * @since 1.1
181      */
182     @Parameter( defaultValue = "false" )
183     protected boolean runOnlyAtExecutionRoot;
184 
185     /**
186      * Used for calculation of execution-root for {@link ProcessRemoteResourcesMojo#runOnlyAtExecutionRoot}.
187      */
188     @Parameter( defaultValue = "${basedir}", readonly = true, required = true )
189     protected File basedir;
190 
191     /**
192      * The character encoding scheme to be applied when filtering resources.
193      */
194     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
195     protected String encoding;
196 
197     /**
198      * The local repository taken from Maven's runtime. Typically <code>$HOME/.m2/repository</code>.
199      */
200     @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
201     private ArtifactRepository localRepository;
202 
203     /**
204      * List of Remote Repositories used by the resolver.
205      */
206     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true )
207     private List<ArtifactRepository> remoteArtifactRepositories;
208 
209     /**
210      * The current Maven project.
211      */
212     @Parameter( defaultValue = "${project}", readonly = true, required = true )
213     private MavenProject project;
214 
215     /**
216      * The directory where processed resources will be placed for packaging.
217      */
218     @Parameter( defaultValue = "${project.build.directory}/maven-shared-archive-resources" )
219     private File outputDirectory;
220 
221     /**
222      * The directory containing extra information appended to the generated resources.
223      */
224     @Parameter( defaultValue = "${basedir}/src/main/appended-resources" )
225     private File appendedResourcesDirectory;
226 
227     /**
228      * Supplemental model data. Useful when processing
229      * artifacts with incomplete POM metadata.
230      * <p/>
231      * By default, this Mojo looks for supplemental model data in the file
232      * "<code>${appendedResourcesDirectory}/supplemental-models.xml</code>".
233      *
234      * @since 1.0-alpha-5
235      */
236     @Parameter
237     private String[] supplementalModels;
238 
239     /**
240      * List of artifacts that are added to the search path when looking
241      * for supplementalModels, expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format.
242      *
243      * @since 1.1
244      */
245     @Parameter
246     private List<String> supplementalModelArtifacts;
247 
248     /**
249      * Map of artifacts to supplemental project object models.
250      */
251     private Map<String, Model> supplementModels;
252 
253     /**
254      * Merges supplemental data model with artifact metadata. Useful when processing artifacts with
255      * incomplete POM metadata.
256      */
257     private ModelInheritanceAssembler inheritanceAssembler = new ModelInheritanceAssembler();
258 
259     /**
260      * The resource bundles that will be retrieved and processed,
261      * expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format.
262      */
263     @Parameter( required = true )
264     private List<String> resourceBundles;
265 
266     /**
267      * Skip remote-resource processing
268      *
269      * @since 1.0-alpha-5
270      */
271     @Parameter( property = "remoteresources.skip", defaultValue = "false" )
272     private boolean skip;
273 
274     /**
275      * Attaches the resources to the main build of the project as a resource directory.
276      *
277      * @since 1.5
278      */
279     @Parameter( defaultValue = "true", property = "attachToMain" )
280     private boolean attachToMain;
281 
282     /**
283      * Attaches the resources to the test build of the project as a resource directory.
284      *
285      * @since 1.5
286      */
287     @Parameter( defaultValue = "true", property = "attachToTest" )
288     private boolean attachToTest;
289 
290     /**
291      * Additional properties to be passed to Velocity.
292      * <p/>
293      * Several properties are automatically added:<ul>
294      * <li><code>project</code> - the current MavenProject </li>
295      * <li><code>projects</code> - the list of dependency projects</li>
296      * <li><code>projectsSortedByOrganization</code> - the list of dependency projects sorted by organization</li>
297      * <li><code>projectTimespan</code> - the timespan of the current project (requires inceptionYear in pom)</li>
298      * <li><code>locator</code> - the ResourceManager that can be used to retrieve additional resources</li>
299      * </ul>
300      * See <a
301      * href="https://maven.apache.org/ref/current/maven-project/apidocs/org/apache/maven/project/MavenProject.html"> the
302      * javadoc for MavenProject</a> for information about the properties on the MavenProject.
303      */
304     @Parameter
305     private Map<String, Object> properties = new HashMap<>();
306 
307     /**
308      * Whether to include properties defined in the project when filtering resources.
309      *
310      * @since 1.2
311      */
312     @Parameter( defaultValue = "false" )
313     protected boolean includeProjectProperties = false;
314 
315     /**
316      * When the result of velocity transformation fits in memory, it is compared with the actual contents on disk
317      * to eliminate unnecessary destination file overwrite. This improves build times since further build steps
318      * typically rely on the modification date.
319      *
320      * @since 1.6
321      */
322     @Parameter( defaultValue = "5242880" )
323     protected int velocityFilterInMemoryThreshold = 5 * 1024 * 1024;
324 
325     /**
326      * The list of resources defined for the project.
327      */
328     @Parameter( defaultValue = "${project.resources}", readonly = true, required = true )
329     private List<Resource> resources;
330 
331     /**
332      * Repository system, needed to create Artifact and Repository objects.
333      */
334     @Component
335     protected RepositorySystem repositorySystem;
336 
337     /**
338      * Artifact Resolver, needed to resolve and download the {@code resourceBundles}.
339      */
340     @Component
341     private ArtifactResolver artifactResolver;
342 
343     /**
344      * Filtering support, for local resources that override those in the remote bundle.
345      */
346     @Component
347     private MavenFileFilter fileFilter;
348 
349     /**
350      * Project artifact factory, needed to create artifacts for the project dependencies.
351      */
352     @Component
353     private ProjectArtifactFactory projectArtifactFactory;
354 
355     /**
356      * The Maven session.
357      */
358     @Parameter( defaultValue = "${session}", readonly = true, required = true )
359     private MavenSession mavenSession;
360 
361     /**
362      * ProjectBuilder, needed to create projects from the artifacts.
363      */
364     @Component( role = ProjectBuilder.class )
365     private ProjectBuilder projectBuilder;
366 
367     /**
368      */
369     @Component
370     private ResourceManager locator;
371 
372     /**
373      * Scope to include. An Empty string indicates all scopes (default is "runtime").
374      *
375      * @since 1.0
376      */
377     @Parameter( property = "includeScope", defaultValue = "runtime" )
378     protected String includeScope;
379 
380     /**
381      * Scope to exclude. An Empty string indicates no scopes (default).
382      *
383      * @since 1.0
384      */
385     @Parameter( property = "excludeScope", defaultValue = "" )
386     protected String excludeScope;
387 
388     /**
389      * When resolving project dependencies, specify the scopes to include.
390      * The default is the same as "includeScope" if there are no exclude scopes set.
391      * Otherwise, it defaults to "test" to grab all the dependencies so the
392      * exclude filters can filter out what is not needed.
393      * 
394      * @since 1.5
395      */
396     @Parameter
397     private String[] resolveScopes;
398 
399     /**
400      * Comma separated list of Artifact names too exclude.
401      *
402      * @since 1.0
403      */
404     @Parameter( property = "excludeArtifactIds", defaultValue = "" )
405     protected String excludeArtifactIds;
406 
407     /**
408      * Comma separated list of Artifact names to include.
409      *
410      * @since 1.0
411      */
412     @Parameter( property = "includeArtifactIds", defaultValue = "" )
413     protected String includeArtifactIds;
414 
415     /**
416      * Comma separated list of GroupId Names to exclude.
417      *
418      * @since 1.0
419      */
420     @Parameter( property = "excludeGroupIds", defaultValue = "" )
421     protected String excludeGroupIds;
422 
423     /**
424      * Comma separated list of GroupIds to include.
425      *
426      * @since 1.0
427      */
428     @Parameter( property = "includeGroupIds", defaultValue = "" )
429     protected String includeGroupIds;
430 
431     /**
432      * If we should exclude transitive dependencies
433      *
434      * @since 1.0
435      */
436     @Parameter( property = "excludeTransitive", defaultValue = "false" )
437     protected boolean excludeTransitive;
438 
439     /**
440      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
441      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
442      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
443      */
444     @Parameter( defaultValue = "${project.build.outputTimestamp}" )
445     private String outputTimestamp;
446 
447     /**
448      */
449     @Component( hint = "default" )
450     protected ProjectDependenciesResolver dependencyResolver;
451 
452     private VelocityEngine velocity;
453 
454     @Override
455     @SuppressWarnings( "unchecked" )
456     public void execute()
457         throws MojoExecutionException
458     {
459         if ( skip )
460         {
461             getLog().info( "Skipping remote resources execution." );
462             return;
463         }
464 
465         if ( StringUtils.isEmpty( encoding ) )
466         {
467             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
468                                + ", i.e. build is platform dependent!" );
469         }
470 
471         if ( runOnlyAtExecutionRoot && !project.isExecutionRoot() )
472         {
473             getLog().info( "Skipping remote-resource generation in this project because it's not the Execution Root" );
474             return;
475         }
476 
477         if ( resolveScopes == null )
478         {
479             resolveScopes =
480                 new String[] { StringUtils.isEmpty( excludeScope ) ? this.includeScope : Artifact.SCOPE_TEST };
481         }
482 
483         if ( supplementalModels == null )
484         {
485             File sups = new File( appendedResourcesDirectory, "supplemental-models.xml" );
486             if ( sups.exists() )
487             {
488                 try
489                 {
490                     supplementalModels = new String[] { sups.toURI().toURL().toString() };
491                 }
492                 catch ( MalformedURLException e )
493                 {
494                     // ignore
495                     getLog().debug( "URL issue with supplemental-models.xml: " + e.toString() );
496                 }
497             }
498         }
499 
500         configureLocator();
501 
502         if ( includeProjectProperties )
503         {
504             final Properties projectProperties = project.getProperties();
505             for ( Object key : projectProperties.keySet() )
506             {
507                 properties.put( key.toString(), projectProperties.get( key ).toString() );
508             }
509         }
510 
511         ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
512         try
513         {
514             validate();
515 
516             List<File> resourceBundleArtifacts = downloadBundles( resourceBundles );
517             supplementModels = loadSupplements( supplementalModels );
518 
519             ClassLoader classLoader = initalizeClassloader( resourceBundleArtifacts );
520 
521             Thread.currentThread().setContextClassLoader( classLoader );
522 
523             velocity = new VelocityEngine();
524             velocity.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this );
525             velocity.setProperty( "resource.loader", "classpath" );
526             velocity.setProperty( "classpath.resource.loader.class", ClasspathResourceLoader.class.getName() );
527             velocity.init();
528 
529             VelocityContext context = buildVelocityContext( properties );
530 
531             processResourceBundles( classLoader, context );
532 
533             if ( outputDirectory.exists() )
534             {
535                 // ----------------------------------------------------------------------------
536                 // Push our newly generated resources directory into the MavenProject so that
537                 // these resources can be picked up by the process-resources phase.
538                 // ----------------------------------------------------------------------------
539                 Resource resource = new Resource();
540                 resource.setDirectory( outputDirectory.getAbsolutePath() );
541                 // MRRESOURCES-61 handle main and test resources separately
542                 if ( attachToMain )
543                 {
544                     project.getResources().add( resource );
545                 }
546                 if ( attachToTest )
547                 {
548                     project.getTestResources().add( resource );
549                 }
550 
551                 // ----------------------------------------------------------------------------
552                 // Write out archiver dot file
553                 // ----------------------------------------------------------------------------
554                 try
555                 {
556                     File dotFile = new File( project.getBuild().getDirectory(), ".plxarc" );
557                     FileUtils.mkdir( dotFile.getParentFile().getAbsolutePath() );
558                     FileUtils.fileWrite( dotFile.getAbsolutePath(), outputDirectory.getName() );
559                 }
560                 catch ( IOException e )
561                 {
562                     throw new MojoExecutionException( "Error creating dot file for archiving instructions.", e );
563                 }
564             }
565         }
566         finally
567         {
568             Thread.currentThread().setContextClassLoader( origLoader );
569         }
570     }
571 
572     private void configureLocator()
573         throws MojoExecutionException
574     {
575         if ( supplementalModelArtifacts != null && !supplementalModelArtifacts.isEmpty() )
576         {
577             List<File> artifacts = downloadBundles( supplementalModelArtifacts );
578 
579             for ( File artifact : artifacts )
580             {
581                 if ( artifact.isDirectory() )
582                 {
583                     locator.addSearchPath( FileResourceLoader.ID, artifact.getAbsolutePath() );
584                 }
585                 else
586                 {
587                     try
588                     {
589                         locator.addSearchPath( "jar", "jar:" + artifact.toURI().toURL().toExternalForm() );
590                     }
591                     catch ( MalformedURLException e )
592                     {
593                         throw new MojoExecutionException( "Could not use jar " + artifact.getAbsolutePath(), e );
594                     }
595                 }
596             }
597 
598         }
599 
600         locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
601         if ( appendedResourcesDirectory != null )
602         {
603             locator.addSearchPath( FileResourceLoader.ID, appendedResourcesDirectory.getAbsolutePath() );
604         }
605         locator.addSearchPath( "url", "" );
606         locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
607     }
608 
609     @SuppressWarnings( "unchecked" )
610     protected List<MavenProject> getProjects()
611     {
612         List<MavenProject> projects = new ArrayList<>();
613 
614         // add filters in well known order, least specific to most specific
615         FilterArtifacts filter = new FilterArtifacts();
616 
617         Set<Artifact> artifacts = resolveProjectArtifacts();
618         if ( this.excludeTransitive )
619         {
620             Set<Artifact> depArtifacts;
621             if ( runOnlyAtExecutionRoot )
622             {
623                 depArtifacts = aggregateProjectDependencyArtifacts();
624             }
625             else
626             {
627                 depArtifacts = project.getDependencyArtifacts();
628             }
629             filter.addFilter( new ProjectTransitivityFilter( depArtifacts, true ) );
630         }
631 
632         filter.addFilter( new ScopeFilter( this.includeScope, this.excludeScope ) );
633         filter.addFilter( new GroupIdFilter( this.includeGroupIds, this.excludeGroupIds ) );
634         filter.addFilter( new ArtifactIdFilter( this.includeArtifactIds, this.excludeArtifactIds ) );
635 
636         // perform filtering
637         try
638         {
639             artifacts = filter.filter( artifacts );
640         }
641         catch ( ArtifactFilterException e )
642         {
643             throw new IllegalStateException( e.getMessage(), e );
644         }
645 
646         for ( Artifact artifact : artifacts )
647         {
648             List<ArtifactRepository> remoteRepo = remoteArtifactRepositories;
649             if ( artifact.isSnapshot() )
650             {
651                 VersionRange rng = VersionRange.createFromVersion( artifact.getBaseVersion() );
652                 artifact = new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(), rng,
653                                                 artifact.getScope(), artifact.getType(), artifact.getClassifier(),
654                                                 artifact.getArtifactHandler(), artifact.isOptional() );
655             }
656 
657             getLog().debug( "Building project for " + artifact );
658             MavenProject p;
659             try
660             {
661                 ProjectBuildingRequest req = new DefaultProjectBuildingRequest()
662                         .setRepositorySession( mavenSession.getRepositorySession() )
663                         .setLocalRepository( localRepository )
664                         .setRemoteRepositories( remoteRepo );
665                 ProjectBuildingResult res = projectBuilder.build( artifact, req );
666                 p = res.getProject();
667             }
668             catch ( ProjectBuildingException e )
669             {
670                 getLog().warn( "Invalid project model for artifact [" + artifact.getArtifactId() + ":"
671                                    + artifact.getGroupId() + ":" + artifact.getVersion() + "]. "
672                                    + "It will be ignored by the remote resources Mojo." );
673                 continue;
674             }
675 
676             String supplementKey =
677                 generateSupplementMapKey( p.getModel().getGroupId(), p.getModel().getArtifactId() );
678 
679             if ( supplementModels.containsKey( supplementKey ) )
680             {
681                 Model mergedModel = mergeModels( p.getModel(), supplementModels.get( supplementKey ) );
682                 MavenProject mergedProject = new MavenProject( mergedModel );
683                 projects.add( mergedProject );
684                 mergedProject.setArtifact( artifact );
685                 mergedProject.setVersion( artifact.getVersion() );
686                 getLog().debug( "Adding project with groupId [" + mergedProject.getGroupId() + "] (supplemented)" );
687             }
688             else
689             {
690                 projects.add( p );
691                 getLog().debug( "Adding project with groupId [" + p.getGroupId() + "]" );
692             }
693         }
694         Collections.sort( projects, new ProjectComparator() );
695         return projects;
696     }
697 
698     private Set<Artifact> resolveProjectArtifacts()
699     {
700         try
701         {
702             if ( runOnlyAtExecutionRoot )
703             {
704                 List<MavenProject> projects = mavenSession.getProjects();
705                 return dependencyResolver.resolve( projects, Arrays.asList( resolveScopes ), mavenSession );
706             }
707             else
708             {
709                 return dependencyResolver.resolve( project, Arrays.asList( resolveScopes ), mavenSession );
710             }
711         }
712         catch ( ArtifactResolutionException e )
713         {
714             throw new IllegalStateException( "Failed to resolve dependencies for one or more projects in the reactor. "
715                 + "Reason: " + e.getMessage(), e );
716         }
717         catch ( ArtifactNotFoundException e )
718         {
719             throw new IllegalStateException( "Failed to resolve dependencies for one or more projects in the reactor. "
720                 + "Reason: " + e.getMessage(), e );
721         }
722     }
723 
724     @SuppressWarnings( "unchecked" )
725     private Set<Artifact> aggregateProjectDependencyArtifacts()
726     {
727         Set<Artifact> artifacts = new LinkedHashSet<>();
728 
729         List<MavenProject> projects = mavenSession.getProjects();
730         for ( MavenProject p : projects )
731         {
732             if ( p.getDependencyArtifacts() == null )
733             {
734                 try
735                 {
736                     Set<Artifact> depArtifacts = projectArtifactFactory.createArtifacts( p );
737 
738                     if ( depArtifacts != null && !depArtifacts.isEmpty() )
739                     {
740                         for ( Artifact artifact : depArtifacts )
741                         {
742                             if ( artifact.getVersion() == null && artifact.getVersionRange() != null )
743                             {
744                                 // Version is required for equality comparison between artifacts,
745                                 // but it is not needed for our purposes of filtering out
746                                 // transitive dependencies (which requires only groupId/artifactId).
747                                 // Therefore set an arbitrary version if missing.
748                                 artifact.setResolvedVersion( Artifact.LATEST_VERSION );
749                             }
750                             artifacts.add( artifact );
751                         }
752                     }
753                 }
754                 catch ( InvalidDependencyVersionException e )
755                 {
756                     throw new IllegalStateException( "Failed to create dependency artifacts for: " + p.getId()
757                         + ". Reason: " + e.getMessage(), e );
758                 }
759             }
760         }
761 
762         return artifacts;
763     }
764 
765     protected Map<Organization, List<MavenProject>> getProjectsSortedByOrganization( List<MavenProject> projects )
766     {
767         Map<Organization, List<MavenProject>> organizations =
768             new TreeMap<>( new OrganizationComparator() );
769         List<MavenProject> unknownOrganization = new ArrayList<>();
770 
771         for ( MavenProject p : projects )
772         {
773             if ( p.getOrganization() != null && StringUtils.isNotEmpty( p.getOrganization().getName() ) )
774             {
775                 List<MavenProject> sortedProjects = organizations.get( p.getOrganization() );
776                 if ( sortedProjects == null )
777                 {
778                     sortedProjects = new ArrayList<>();
779                 }
780                 sortedProjects.add( p );
781 
782                 organizations.put( p.getOrganization(), sortedProjects );
783             }
784             else
785             {
786                 unknownOrganization.add( p );
787             }
788         }
789         if ( !unknownOrganization.isEmpty() )
790         {
791             Organization unknownOrg = new Organization();
792             unknownOrg.setName( "an unknown organization" );
793             organizations.put( unknownOrg, unknownOrganization );
794         }
795 
796         return organizations;
797     }
798 
799     protected boolean copyResourceIfExists( File file, String relFileName, VelocityContext context )
800         throws IOException, MojoExecutionException
801     {
802         for ( Resource resource : resources )
803         {
804             File resourceDirectory = new File( resource.getDirectory() );
805 
806             if ( !resourceDirectory.exists() )
807             {
808                 continue;
809             }
810 
811             // TODO - really should use the resource includes/excludes and name mapping
812             File source = new File( resourceDirectory, relFileName );
813             File templateSource = new File( resourceDirectory, relFileName + TEMPLATE_SUFFIX );
814 
815             if ( !source.exists() && templateSource.exists() )
816             {
817                 source = templateSource;
818             }
819 
820             if ( source.exists() && !source.equals( file ) )
821             {
822                 if ( source == templateSource )
823                 {
824                     try ( DeferredFileOutputStream os = 
825                                     new DeferredFileOutputStream( velocityFilterInMemoryThreshold, file ) )
826                     {
827                         try ( Reader reader = getReader( source ); Writer writer = getWriter( os ) )
828                         {
829                             velocity.evaluate( context, writer, "", reader );
830                         }
831                         catch ( ParseErrorException | MethodInvocationException | ResourceNotFoundException e )
832                         {
833                             throw new MojoExecutionException( "Error rendering velocity resource: " + source, e );
834                         }
835                         fileWriteIfDiffers( os );
836                     }
837                 }
838                 else if ( resource.isFiltering() )
839                 {
840 
841                     MavenFileFilterRequest req = setupRequest( resource, source, file );
842 
843                     try
844                     {
845                         fileFilter.copyFile( req );
846                     }
847                     catch ( MavenFilteringException e )
848                     {
849                         throw new MojoExecutionException( "Error filtering resource: " + source, e );
850                     }
851                 }
852                 else
853                 {
854                     FileUtils.copyFile( source, file );
855                 }
856 
857                 // exclude the original (so eclipse doesn't complain about duplicate resources)
858                 resource.addExclude( relFileName );
859 
860                 return true;
861             }
862 
863         }
864         return false;
865     }
866 
867     private Reader getReader( File source ) throws IOException
868     {
869         if ( encoding != null )
870         {
871             return new InputStreamReader( new FileInputStream( source ), encoding );
872         }
873         else
874         {
875             return ReaderFactory.newPlatformReader( source );
876         }
877     }
878     
879     private Writer getWriter( OutputStream os ) throws IOException
880     {
881         if ( encoding != null )
882         {
883             return new OutputStreamWriter( os, encoding );
884         }
885         else
886         {
887             return WriterFactory.newPlatformWriter( os );
888         }
889     }
890 
891     /**
892      * If the transformation result fits in memory and the destination file already exists
893      * then both are compared.
894      * <p>If destination file is byte-by-byte equal, then it is not overwritten.
895      * This improves subsequent compilation times since upstream plugins property see that
896      * the resource was not modified.
897      * <p>Note: the method should be called after {@link org.apache.commons.io.output.DeferredFileOutputStream#close}
898      *
899      * @param outStream Deferred stream
900      * @throws IOException
901      */
902     private void fileWriteIfDiffers( DeferredFileOutputStream outStream )
903         throws IOException
904     {
905         File file = outStream.getFile();
906         if ( outStream.isThresholdExceeded() )
907         {
908             getLog().info( "File " + file + " was overwritten due to content limit threshold "
909                     + outStream.getThreshold() + " reached" );
910             return;
911         }
912         boolean needOverwrite = true;
913 
914         if ( file.exists() )
915         {
916             try ( InputStream is = new FileInputStream( file ) )
917             {
918                 final InputStream newContents = new ByteArrayInputStream( outStream.getData() );
919                 needOverwrite = !IOUtil.contentEquals( is, newContents );
920                 if ( getLog().isDebugEnabled() )
921                 {
922                     getLog().debug( "File " + file + " contents "
923                                         + ( needOverwrite ? "differs" : "does not differ" ) );
924                 }
925             }
926         }
927 
928         if ( !needOverwrite )
929         {
930             getLog().debug( "File " + file + " is up to date" );
931             return;
932         }
933         getLog().debug( "Writing " + file );
934         
935         try ( OutputStream os = new FileOutputStream( file ) )
936         {
937             outStream.writeTo( os );
938         }
939     }
940 
941     private MavenFileFilterRequest setupRequest( Resource resource, File source, File file )
942     {
943         MavenFileFilterRequest req = new MavenFileFilterRequest();
944         req.setFrom( source );
945         req.setTo( file );
946         req.setFiltering( resource.isFiltering() );
947 
948         req.setMavenProject( project );
949         req.setMavenSession( mavenSession );
950         req.setInjectProjectBuildFilters( true );
951 
952         if ( encoding != null )
953         {
954             req.setEncoding( encoding );
955         }
956 
957         if ( filterDelimiters != null && !filterDelimiters.isEmpty() )
958         {
959             LinkedHashSet<String> delims = new LinkedHashSet<>();
960             if ( useDefaultFilterDelimiters )
961             {
962                 delims.addAll( req.getDelimiters() );
963             }
964 
965             for ( String delim : filterDelimiters )
966             {
967                 if ( delim == null )
968                 {
969                     delims.add( "${*}" );
970                 }
971                 else
972                 {
973                     delims.add( delim );
974                 }
975             }
976 
977             req.setDelimiters( delims );
978         }
979 
980         return req;
981     }
982 
983     protected void validate()
984         throws MojoExecutionException
985     {
986         int bundleCount = 1;
987 
988         for ( String artifactDescriptor : resourceBundles )
989         {
990             // groupId:artifactId:version, groupId:artifactId:version:type
991             // or groupId:artifactId:version:type:classifier
992             String[] s = StringUtils.split( artifactDescriptor, ":" );
993 
994             if ( s.length < 3 || s.length > 5 )
995             {
996                 String position;
997 
998                 if ( bundleCount == 1 )
999                 {
1000                     position = "1st";
1001                 }
1002                 else if ( bundleCount == 2 )
1003                 {
1004                     position = "2nd";
1005                 }
1006                 else if ( bundleCount == 3 )
1007                 {
1008                     position = "3rd";
1009                 }
1010                 else
1011                 {
1012                     position = bundleCount + "th";
1013                 }
1014 
1015                 throw new MojoExecutionException( "The " + position
1016                     + " resource bundle configured must specify a groupId, artifactId, "
1017                     + " version and, optionally, type and classifier for a remote resource bundle. "
1018                     + "Must be of the form <resourceBundle>groupId:artifactId:version</resourceBundle>, "
1019                     + "<resourceBundle>groupId:artifactId:version:type</resourceBundle> or "
1020                     + "<resourceBundle>groupId:artifactId:version:type:classifier</resourceBundle>" );
1021             }
1022 
1023             bundleCount++;
1024         }
1025 
1026     }
1027 
1028     private static final String KEY_PROJECTS = "projects";
1029     private static final String KEY_PROJECTS_ORGS = "projectsSortedByOrganization";
1030 
1031     protected VelocityContext buildVelocityContext( Map<String, Object> properties )
1032         throws MojoExecutionException
1033     {
1034         // the following properties are expensive to calculate, so we provide them lazily
1035         VelocityContext context = new VelocityContext( properties )
1036         {
1037             @Override
1038             public Object internalGet( String key )
1039             {
1040                 Object result = super.internalGet( key );
1041                 if ( result == null && key != null && key.startsWith( KEY_PROJECTS ) && containsKey( key ) )
1042                 {
1043                     // calculate and put projects* properties
1044                     List<MavenProject> projects = getProjects();
1045                     put( KEY_PROJECTS, projects );
1046                     put( KEY_PROJECTS_ORGS, getProjectsSortedByOrganization( projects ) );
1047                     return super.internalGet( key );
1048                 }
1049                 return result;
1050             }
1051         };
1052         // to have a consistent getKeys()/containsKey() behaviour, keys must be present from the start
1053         context.put( KEY_PROJECTS, null );
1054         context.put( KEY_PROJECTS_ORGS, null );
1055         // the following properties are cheap to calculate, so we provide them eagerly
1056 
1057         // Reproducible Builds: try to use reproducible output timestamp 
1058         MavenArchiver archiver = new MavenArchiver();
1059         Date outputDate = archiver.parseOutputTimestamp( outputTimestamp );
1060 
1061         String inceptionYear = project.getInceptionYear();
1062         String year = new SimpleDateFormat( "yyyy" ).format( ( outputDate == null ) ? new Date() : outputDate );
1063 
1064         if ( StringUtils.isEmpty( inceptionYear ) )
1065         {
1066             if ( getLog().isDebugEnabled() )
1067             {
1068                 getLog().debug( "inceptionYear not specified, defaulting to " + year );
1069             }
1070 
1071             inceptionYear = year;
1072         }
1073         context.put( "project", project );
1074         context.put( "presentYear", year );
1075         context.put( "locator", locator );
1076 
1077         if ( inceptionYear.equals( year ) )
1078         {
1079             context.put( "projectTimespan", year );
1080         }
1081         else
1082         {
1083             context.put( "projectTimespan", inceptionYear + "-" + year );
1084         }
1085         return context;
1086     }
1087 
1088     private List<File> downloadBundles( List<String> bundles )
1089         throws MojoExecutionException
1090     {
1091         List<File> bundleArtifacts = new ArrayList<>();
1092 
1093         for ( String artifactDescriptor : bundles )
1094         {
1095             getLog().info( "Preparing remote bundle " + artifactDescriptor );
1096             // groupId:artifactId:version[:type[:classifier]]
1097             String[] s = artifactDescriptor.split( ":" );
1098 
1099             File artifactFile = null;
1100             // check if the artifact is part of the reactor
1101             if ( mavenSession != null )
1102             {
1103                 List<MavenProject> list = mavenSession.getProjects();
1104                 for ( MavenProject p : list )
1105                 {
1106                     if ( s[0].equals( p.getGroupId() ) && s[1].equals( p.getArtifactId() )
1107                         && s[2].equals( p.getVersion() ) )
1108                     {
1109                         if ( s.length >= 4 && "test-jar".equals( s[3] ) )
1110                         {
1111                             artifactFile = new File( p.getBuild().getTestOutputDirectory() );
1112                         }
1113                         else
1114                         {
1115                             artifactFile = new File( p.getBuild().getOutputDirectory() );
1116                         }
1117                     }
1118                 }
1119             }
1120             if ( artifactFile == null || !artifactFile.exists() )
1121             {
1122                 String type = ( s.length >= 4 ? s[3] : "jar" );
1123                 String classifier = ( s.length == 5 ? s[4] : null );
1124                 Artifact artifact = repositorySystem.createArtifactWithClassifier( s[0], s[1], s[2], type, classifier );
1125 
1126                 try
1127                 {
1128                     ArtifactResult result = artifactResolver.resolveArtifact(
1129                             mavenSession.getProjectBuildingRequest(), artifact );
1130                     artifactFile = result.getArtifact().getFile();
1131                 }
1132                 catch ( ArtifactResolverException e )
1133                 {
1134                     throw new MojoExecutionException( "Error processing remote resources", e );
1135                 }
1136 
1137             }
1138             bundleArtifacts.add( artifactFile );
1139         }
1140 
1141         return bundleArtifacts;
1142     }
1143 
1144     private ClassLoader initalizeClassloader( List<File> artifacts )
1145         throws MojoExecutionException
1146     {
1147         RemoteResourcesClassLoader cl = new RemoteResourcesClassLoader( null );
1148         try
1149         {
1150             for ( File artifact : artifacts )
1151             {
1152                 cl.addURL( artifact.toURI().toURL() );
1153             }
1154             return cl;
1155         }
1156         catch ( MalformedURLException e )
1157         {
1158             throw new MojoExecutionException( "Unable to configure resources classloader: " + e.getMessage(), e );
1159         }
1160     }
1161 
1162     protected void processResourceBundles( ClassLoader classLoader, VelocityContext context )
1163         throws MojoExecutionException
1164     {
1165         List<Map.Entry<String, RemoteResourcesBundle>> remoteResources =
1166             new ArrayList<>();
1167         int bundleCount = 0;
1168         int resourceCount = 0;
1169 
1170         // list remote resources form bundles
1171         try
1172         {
1173             RemoteResourcesBundleXpp3Reader bundleReader = new RemoteResourcesBundleXpp3Reader();
1174 
1175             for ( Enumeration<URL> e =
1176                 classLoader.getResources( BundleRemoteResourcesMojo.RESOURCES_MANIFEST ); e.hasMoreElements(); )
1177             {
1178                 URL url = e.nextElement();
1179                 bundleCount++;
1180                 getLog().debug( "processResourceBundle on bundle#" + bundleCount + " " + url );
1181 
1182                 RemoteResourcesBundle bundle;
1183                 
1184                 try ( InputStream in = url.openStream() )
1185                 {
1186                     bundle = bundleReader.read( in );
1187                 }
1188 
1189                 int n = 0;
1190                 for ( String bundleResource : bundle.getRemoteResources() )
1191                 {
1192                     n++;
1193                     resourceCount++;
1194                     getLog().debug( "bundle#" + bundleCount + " resource#" + n + " " + bundleResource );
1195                     remoteResources.add( new AbstractMap.SimpleEntry<>( bundleResource,
1196                                                                                                      bundle ) );
1197                 }
1198             }
1199         }
1200         catch ( IOException ioe )
1201         {
1202             throw new MojoExecutionException( "Error finding remote resources manifests", ioe );
1203         }
1204         catch ( XmlPullParserException xppe )
1205         {
1206             throw new MojoExecutionException( "Error parsing remote resource bundle descriptor.", xppe );
1207         }
1208 
1209         getLog().info( "Copying " + resourceCount + " resource" + ( ( resourceCount > 1 ) ? "s" : "" ) + " from "
1210             + bundleCount + " bundle" + ( ( bundleCount > 1 ) ? "s" : "" ) + "." );
1211 
1212         String velocityResource = null;
1213         try
1214         {
1215 
1216             for ( Map.Entry<String, RemoteResourcesBundle> entry : remoteResources )
1217             {
1218                 String bundleResource = entry.getKey();
1219                 RemoteResourcesBundle bundle = entry.getValue();
1220 
1221                 String projectResource = bundleResource;
1222 
1223                 boolean doVelocity = false;
1224                 if ( projectResource.endsWith( TEMPLATE_SUFFIX ) )
1225                 {
1226                     projectResource = projectResource.substring( 0, projectResource.length() - 3 );
1227                     velocityResource = bundleResource;
1228                     doVelocity = true;
1229                 }
1230 
1231                 // Don't overwrite resource that are already being provided.
1232 
1233                 File f = new File( outputDirectory, projectResource );
1234 
1235                 FileUtils.mkdir( f.getParentFile().getAbsolutePath() );
1236 
1237                 if ( !copyResourceIfExists( f, projectResource, context ) )
1238                 {
1239                     if ( doVelocity )
1240                     {
1241                         try ( DeferredFileOutputStream os =
1242                                         new DeferredFileOutputStream( velocityFilterInMemoryThreshold, f ) )
1243                         {
1244                             try ( Writer writer = bundle.getSourceEncoding() == null ? new OutputStreamWriter( os )
1245                                             : new OutputStreamWriter( os, bundle.getSourceEncoding() ) )
1246                             {
1247                                 if ( bundle.getSourceEncoding() == null )
1248                                 {
1249                                     // TODO: Is this correct? Shouldn't we behave like the rest of maven and fail
1250                                     // down to JVM default instead ISO-8859-1 ?
1251                                     velocity.mergeTemplate( bundleResource, "ISO-8859-1", context, writer );
1252                                 }
1253                                 else
1254                                 {
1255                                     velocity.mergeTemplate( bundleResource, bundle.getSourceEncoding(), context,
1256                                                             writer );
1257                                 }
1258                             }
1259                             fileWriteIfDiffers( os );
1260                         }
1261                     }
1262                     else
1263                     {
1264                         URL resUrl = classLoader.getResource( bundleResource );
1265                         if ( resUrl != null )
1266                         {
1267                             FileUtils.copyURLToFile( resUrl, f );
1268                         }
1269                     }
1270 
1271                     File appendedResourceFile = new File( appendedResourcesDirectory, projectResource );
1272                     File appendedVmResourceFile = new File( appendedResourcesDirectory, projectResource + ".vm" );
1273 
1274                     if ( appendedResourceFile.exists() )
1275                     {
1276                         getLog().info( "Copying appended resource: " + projectResource );
1277                         try ( InputStream in = new FileInputStream( appendedResourceFile );
1278                               OutputStream out = new FileOutputStream( f, true ) )
1279                         {
1280                             IOUtil.copy( in, out );
1281                         }
1282                         
1283                     }
1284                     else if ( appendedVmResourceFile.exists() )
1285                     {
1286                         getLog().info( "Filtering appended resource: " + projectResource + ".vm" );
1287                         
1288 
1289                         try ( Reader reader = new FileReader( appendedVmResourceFile ); 
1290                               Writer writer = getWriter( bundle, f ) )
1291                         {
1292                             Velocity.init();
1293                             Velocity.evaluate( context, writer, "remote-resources", reader );
1294                         }
1295                     }
1296                 }
1297             }
1298         }
1299         catch ( IOException ioe )
1300         {
1301             throw new MojoExecutionException( "Error reading remote resource", ioe );
1302         }
1303         catch ( VelocityException e )
1304         {
1305             throw new MojoExecutionException( "Error rendering Velocity resource '" + velocityResource + "'", e );
1306         }
1307     }
1308 
1309     private Writer getWriter( RemoteResourcesBundle bundle, File f )
1310         throws IOException, UnsupportedEncodingException, FileNotFoundException
1311     {
1312         Writer writer;
1313         if ( bundle.getSourceEncoding() == null )
1314         {
1315             writer = new PrintWriter( new FileWriter( f, true ) );
1316         }
1317         else
1318         {
1319             writer = new PrintWriter( new OutputStreamWriter( new FileOutputStream( f, true ),
1320                                                               bundle.getSourceEncoding() ) );
1321         }
1322         return writer;
1323     }
1324 
1325     protected Model getSupplement( Xpp3Dom supplementModelXml )
1326         throws MojoExecutionException
1327     {
1328         MavenXpp3Reader modelReader = new MavenXpp3Reader();
1329         Model model = null;
1330 
1331         try
1332         {
1333             model = modelReader.read( new StringReader( supplementModelXml.toString() ) );
1334             String groupId = model.getGroupId();
1335             String artifactId = model.getArtifactId();
1336 
1337             if ( groupId == null || groupId.trim().equals( "" ) )
1338             {
1339                 throw new MojoExecutionException( "Supplemental project XML "
1340                     + "requires that a <groupId> element be present." );
1341             }
1342 
1343             if ( artifactId == null || artifactId.trim().equals( "" ) )
1344             {
1345                 throw new MojoExecutionException( "Supplemental project XML "
1346                     + "requires that a <artifactId> element be present." );
1347             }
1348         }
1349         catch ( IOException e )
1350         {
1351             getLog().warn( "Unable to read supplemental XML: " + e.getMessage(), e );
1352         }
1353         catch ( XmlPullParserException e )
1354         {
1355             getLog().warn( "Unable to parse supplemental XML: " + e.getMessage(), e );
1356         }
1357 
1358         return model;
1359     }
1360 
1361     protected Model mergeModels( Model parent, Model child )
1362     {
1363         inheritanceAssembler.assembleModelInheritance( child, parent );
1364         return child;
1365     }
1366 
1367     private static String generateSupplementMapKey( String groupId, String artifactId )
1368     {
1369         return groupId.trim() + ":" + artifactId.trim();
1370     }
1371 
1372     private Map<String, Model> loadSupplements( String models[] )
1373         throws MojoExecutionException
1374     {
1375         if ( models == null )
1376         {
1377             getLog().debug( "Supplemental data models won't be loaded.  " + "No models specified." );
1378             return Collections.emptyMap();
1379         }
1380 
1381         List<Supplement> supplements = new ArrayList<>();
1382         for ( String set : models )
1383         {
1384             getLog().debug( "Preparing ruleset: " + set );
1385             try
1386             {
1387                 File f = locator.getResourceAsFile( set, getLocationTemp( set ) );
1388 
1389                 if ( null == f || !f.exists() )
1390                 {
1391                     throw new MojoExecutionException( "Cold not resolve " + set );
1392                 }
1393                 if ( !f.canRead() )
1394                 {
1395                     throw new MojoExecutionException( "Supplemental data models won't be loaded. " + "File "
1396                         + f.getAbsolutePath() + " cannot be read, check permissions on the file." );
1397                 }
1398 
1399                 getLog().debug( "Loading supplemental models from " + f.getAbsolutePath() );
1400 
1401                 SupplementalDataModelXpp3Reader reader = new SupplementalDataModelXpp3Reader();
1402                 SupplementalDataModel supplementalModel = reader.read( new FileReader( f ) );
1403                 supplements.addAll( supplementalModel.getSupplement() );
1404             }
1405             catch ( Exception e )
1406             {
1407                 String msg = "Error loading supplemental data models: " + e.getMessage();
1408                 getLog().error( msg, e );
1409                 throw new MojoExecutionException( msg, e );
1410             }
1411         }
1412 
1413         getLog().debug( "Loading supplements complete." );
1414 
1415         Map<String, Model> supplementMap = new HashMap<>();
1416         for ( Supplement sd : supplements )
1417         {
1418             Xpp3Dom dom = (Xpp3Dom) sd.getProject();
1419 
1420             Model m = getSupplement( dom );
1421             supplementMap.put( generateSupplementMapKey( m.getGroupId(), m.getArtifactId() ), m );
1422         }
1423 
1424         return supplementMap;
1425     }
1426 
1427     /**
1428      * Convenience method to get the location of the specified file name.
1429      *
1430      * @param name the name of the file whose location is to be resolved
1431      * @return a String that contains the absolute file name of the file
1432      */
1433     private String getLocationTemp( String name )
1434     {
1435         String loc = name;
1436         if ( loc.indexOf( '/' ) != -1 )
1437         {
1438             loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
1439         }
1440         if ( loc.indexOf( '\\' ) != -1 )
1441         {
1442             loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
1443         }
1444         getLog().debug( "Before: " + name + " After: " + loc );
1445         return loc;
1446     }
1447 
1448     class OrganizationComparator
1449         implements Comparator<Organization>
1450     {
1451         @Override
1452         public int compare( Organization org1, Organization org2 )
1453         {
1454             int i = compareStrings( org1.getName(), org2.getName() );
1455             if ( i == 0 )
1456             {
1457                 i = compareStrings( org1.getUrl(), org2.getUrl() );
1458             }
1459             return i;
1460         }
1461 
1462         public boolean equals( Organization o1, Organization o2 )
1463         {
1464             return compare( o1, o2 ) == 0;
1465         }
1466 
1467         private int compareStrings( String s1, String s2 )
1468         {
1469             if ( s1 == null && s2 == null )
1470             {
1471                 return 0;
1472             }
1473             else if ( s1 == null && s2 != null )
1474             {
1475                 return 1;
1476             }
1477             else if ( s1 != null && s2 == null )
1478             {
1479                 return -1;
1480             }
1481 
1482             return s1.compareToIgnoreCase( s2 );
1483         }
1484     }
1485 
1486     class ProjectComparator
1487         implements Comparator<MavenProject>
1488     {
1489         @Override
1490         public int compare( MavenProject p1, MavenProject p2 )
1491         {
1492             return p1.getArtifact().compareTo( p2.getArtifact() );
1493         }
1494 
1495         public boolean equals( MavenProject p1, MavenProject p2 )
1496         {
1497             return p1.getArtifact().equals( p2.getArtifact() );
1498         }
1499     }
1500 
1501     /* LogChute methods */
1502     @Override
1503     public void init( RuntimeServices rs )
1504         throws Exception
1505     {
1506     }
1507 
1508     @Override
1509     public void log( int level, String message )
1510     {
1511         switch ( level )
1512         {
1513             case LogChute.WARN_ID:
1514                 getLog().warn( message );
1515                 break;
1516             case LogChute.INFO_ID:
1517                 // velocity info messages are too verbose, just consider them as debug messages...
1518                 getLog().debug( message );
1519                 break;
1520             case LogChute.DEBUG_ID:
1521                 getLog().debug( message );
1522                 break;
1523             case LogChute.ERROR_ID:
1524                 getLog().error( message );
1525                 break;
1526             default:
1527                 getLog().debug( message );
1528                 break;
1529         }
1530     }
1531 
1532     @Override
1533     public void log( int level, String message, Throwable t )
1534     {
1535         switch ( level )
1536         {
1537             case LogChute.WARN_ID:
1538                 getLog().warn( message, t );
1539                 break;
1540             case LogChute.INFO_ID:
1541                 // velocity info messages are too verbose, just consider them as debug messages...
1542                 getLog().debug( message, t );
1543                 break;
1544             case LogChute.DEBUG_ID:
1545                 getLog().debug( message, t );
1546                 break;
1547             case LogChute.ERROR_ID:
1548                 getLog().error( message, t );
1549                 break;
1550             default:
1551                 getLog().debug( message, t );
1552                 break;
1553         }
1554     }
1555 
1556     @Override
1557     public boolean isLevelEnabled( int level )
1558     {
1559         return false;
1560     }
1561 
1562 }