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