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