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