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