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