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