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