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.DefaultArtifact;
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.versioning.VersionRange;
66 import org.apache.maven.execution.MavenSession;
67 import org.apache.maven.lifecycle.internal.ProjectArtifactFactory;
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.DefaultProjectBuildingRequest;
81 import org.apache.maven.project.MavenProject;
82 import org.apache.maven.project.ProjectBuilder;
83 import org.apache.maven.project.ProjectBuildingException;
84 import org.apache.maven.project.ProjectBuildingRequest;
85 import org.apache.maven.project.ProjectBuildingResult;
86 import org.apache.maven.project.artifact.InvalidDependencyVersionException;
87 import org.apache.maven.repository.RepositorySystem;
88 import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
89 import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter;
90 import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
91 import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
92 import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
93 import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
94 import org.apache.maven.shared.filtering.MavenFileFilter;
95 import org.apache.maven.shared.filtering.MavenFileFilterRequest;
96 import org.apache.maven.shared.filtering.MavenFilteringException;
97 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
98 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
99 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
100 import org.apache.velocity.VelocityContext;
101 import org.apache.velocity.app.Velocity;
102 import org.apache.velocity.app.VelocityEngine;
103 import org.apache.velocity.exception.MethodInvocationException;
104 import org.apache.velocity.exception.ParseErrorException;
105 import org.apache.velocity.exception.ResourceNotFoundException;
106 import org.apache.velocity.exception.VelocityException;
107 import org.apache.velocity.runtime.RuntimeConstants;
108 import org.apache.velocity.runtime.RuntimeServices;
109 import org.apache.velocity.runtime.log.LogChute;
110 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
111 import org.codehaus.plexus.resource.ResourceManager;
112 import org.codehaus.plexus.resource.loader.FileResourceLoader;
113 import org.codehaus.plexus.util.FileUtils;
114 import org.codehaus.plexus.util.IOUtil;
115 import org.codehaus.plexus.util.ReaderFactory;
116 import org.codehaus.plexus.util.StringUtils;
117 import org.codehaus.plexus.util.WriterFactory;
118 import org.codehaus.plexus.util.xml.Xpp3Dom;
119 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 @Mojo( name = "process", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true )
139 public class ProcessRemoteResourcesMojo
140 extends AbstractMojo
141 implements LogChute
142 {
143
144 private static final String TEMPLATE_SUFFIX = ".vm";
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 @Parameter
168 protected List<String> filterDelimiters;
169
170
171
172
173 @Parameter( defaultValue = "true" )
174 protected boolean useDefaultFilterDelimiters;
175
176
177
178
179
180
181
182 @Parameter( defaultValue = "false" )
183 protected boolean runOnlyAtExecutionRoot;
184
185
186
187
188 @Parameter( defaultValue = "${basedir}", readonly = true, required = true )
189 protected File basedir;
190
191
192
193
194 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
195 protected String encoding;
196
197
198
199
200 @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
201 private ArtifactRepository localRepository;
202
203
204
205
206 @Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true )
207 private List<ArtifactRepository> remoteArtifactRepositories;
208
209
210
211
212 @Parameter( defaultValue = "${project}", readonly = true, required = true )
213 private MavenProject project;
214
215
216
217
218 @Parameter( defaultValue = "${project.build.directory}/maven-shared-archive-resources" )
219 private File outputDirectory;
220
221
222
223
224 @Parameter( defaultValue = "${basedir}/src/main/appended-resources" )
225 private File appendedResourcesDirectory;
226
227
228
229
230
231
232
233
234
235
236 @Parameter
237 private String[] supplementalModels;
238
239
240
241
242
243
244
245 @Parameter
246 private List<String> supplementalModelArtifacts;
247
248
249
250
251 private Map<String, Model> supplementModels;
252
253
254
255
256
257 private ModelInheritanceAssembler inheritanceAssembler = new ModelInheritanceAssembler();
258
259
260
261
262
263 @Parameter( required = true )
264 private List<String> resourceBundles;
265
266
267
268
269
270
271 @Parameter( property = "remoteresources.skip", defaultValue = "false" )
272 private boolean skip;
273
274
275
276
277
278
279 @Parameter( defaultValue = "true", property = "attachToMain" )
280 private boolean attachToMain;
281
282
283
284
285
286
287 @Parameter( defaultValue = "true", property = "attachToTest" )
288 private boolean attachToTest;
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304 @Parameter
305 private Map<String, Object> properties = new HashMap<>();
306
307
308
309
310
311
312 @Parameter( defaultValue = "false" )
313 protected boolean includeProjectProperties = false;
314
315
316
317
318
319
320
321
322 @Parameter( defaultValue = "5242880" )
323 protected int velocityFilterInMemoryThreshold = 5 * 1024 * 1024;
324
325
326
327
328 @Parameter( defaultValue = "${project.resources}", readonly = true, required = true )
329 private List<Resource> resources;
330
331
332
333
334 @Component
335 protected RepositorySystem repositorySystem;
336
337
338
339
340 @Component
341 private ArtifactResolver artifactResolver;
342
343
344
345
346 @Component
347 private MavenFileFilter fileFilter;
348
349
350
351
352 @Component
353 private ProjectArtifactFactory projectArtifactFactory;
354
355
356
357
358 @Parameter( defaultValue = "${session}", readonly = true, required = true )
359 private MavenSession mavenSession;
360
361
362
363
364 @Component( role = ProjectBuilder.class )
365 private ProjectBuilder projectBuilder;
366
367
368
369 @Component
370 private ResourceManager locator;
371
372
373
374
375
376
377 @Parameter( property = "includeScope", defaultValue = "runtime" )
378 protected String includeScope;
379
380
381
382
383
384
385 @Parameter( property = "excludeScope", defaultValue = "" )
386 protected String excludeScope;
387
388
389
390
391
392
393
394
395
396 @Parameter
397 private String[] resolveScopes;
398
399
400
401
402
403
404 @Parameter( property = "excludeArtifactIds", defaultValue = "" )
405 protected String excludeArtifactIds;
406
407
408
409
410
411
412 @Parameter( property = "includeArtifactIds", defaultValue = "" )
413 protected String includeArtifactIds;
414
415
416
417
418
419
420 @Parameter( property = "excludeGroupIds", defaultValue = "" )
421 protected String excludeGroupIds;
422
423
424
425
426
427
428 @Parameter( property = "includeGroupIds", defaultValue = "" )
429 protected String includeGroupIds;
430
431
432
433
434
435
436 @Parameter( property = "excludeTransitive", defaultValue = "false" )
437 protected boolean excludeTransitive;
438
439
440
441
442
443
444 @Parameter( defaultValue = "${project.build.outputTimestamp}" )
445 private String outputTimestamp;
446
447
448
449 @Component( hint = "default" )
450 protected ProjectDependenciesResolver dependencyResolver;
451
452 private VelocityEngine velocity;
453
454 @Override
455 @SuppressWarnings( "unchecked" )
456 public void execute()
457 throws MojoExecutionException
458 {
459 if ( skip )
460 {
461 getLog().info( "Skipping remote resources execution." );
462 return;
463 }
464
465 if ( StringUtils.isEmpty( encoding ) )
466 {
467 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
468 + ", i.e. build is platform dependent!" );
469 }
470
471 if ( runOnlyAtExecutionRoot && !project.isExecutionRoot() )
472 {
473 getLog().info( "Skipping remote-resource generation in this project because it's not the Execution Root" );
474 return;
475 }
476
477 if ( resolveScopes == null )
478 {
479 resolveScopes =
480 new String[] { StringUtils.isEmpty( excludeScope ) ? this.includeScope : Artifact.SCOPE_TEST };
481 }
482
483 if ( supplementalModels == null )
484 {
485 File sups = new File( appendedResourcesDirectory, "supplemental-models.xml" );
486 if ( sups.exists() )
487 {
488 try
489 {
490 supplementalModels = new String[] { sups.toURI().toURL().toString() };
491 }
492 catch ( MalformedURLException e )
493 {
494
495 getLog().debug( "URL issue with supplemental-models.xml: " + e.toString() );
496 }
497 }
498 }
499
500 configureLocator();
501
502 if ( includeProjectProperties )
503 {
504 final Properties projectProperties = project.getProperties();
505 for ( Object key : projectProperties.keySet() )
506 {
507 properties.put( key.toString(), projectProperties.get( key ).toString() );
508 }
509 }
510
511 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
512 try
513 {
514 validate();
515
516 List<File> resourceBundleArtifacts = downloadBundles( resourceBundles );
517 supplementModels = loadSupplements( supplementalModels );
518
519 ClassLoader classLoader = initalizeClassloader( resourceBundleArtifacts );
520
521 Thread.currentThread().setContextClassLoader( classLoader );
522
523 velocity = new VelocityEngine();
524 velocity.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this );
525 velocity.setProperty( "resource.loader", "classpath" );
526 velocity.setProperty( "classpath.resource.loader.class", ClasspathResourceLoader.class.getName() );
527 velocity.init();
528
529 VelocityContext context = buildVelocityContext( properties );
530
531 processResourceBundles( classLoader, context );
532
533 if ( outputDirectory.exists() )
534 {
535
536
537
538
539 Resource resource = new Resource();
540 resource.setDirectory( outputDirectory.getAbsolutePath() );
541
542 if ( attachToMain )
543 {
544 project.getResources().add( resource );
545 }
546 if ( attachToTest )
547 {
548 project.getTestResources().add( resource );
549 }
550
551
552
553
554 try
555 {
556 File dotFile = new File( project.getBuild().getDirectory(), ".plxarc" );
557 FileUtils.mkdir( dotFile.getParentFile().getAbsolutePath() );
558 FileUtils.fileWrite( dotFile.getAbsolutePath(), outputDirectory.getName() );
559 }
560 catch ( IOException e )
561 {
562 throw new MojoExecutionException( "Error creating dot file for archiving instructions.", e );
563 }
564 }
565 }
566 finally
567 {
568 Thread.currentThread().setContextClassLoader( origLoader );
569 }
570 }
571
572 private void configureLocator()
573 throws MojoExecutionException
574 {
575 if ( supplementalModelArtifacts != null && !supplementalModelArtifacts.isEmpty() )
576 {
577 List<File> artifacts = downloadBundles( supplementalModelArtifacts );
578
579 for ( File artifact : artifacts )
580 {
581 if ( artifact.isDirectory() )
582 {
583 locator.addSearchPath( FileResourceLoader.ID, artifact.getAbsolutePath() );
584 }
585 else
586 {
587 try
588 {
589 locator.addSearchPath( "jar", "jar:" + artifact.toURI().toURL().toExternalForm() );
590 }
591 catch ( MalformedURLException e )
592 {
593 throw new MojoExecutionException( "Could not use jar " + artifact.getAbsolutePath(), e );
594 }
595 }
596 }
597
598 }
599
600 locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
601 if ( appendedResourcesDirectory != null )
602 {
603 locator.addSearchPath( FileResourceLoader.ID, appendedResourcesDirectory.getAbsolutePath() );
604 }
605 locator.addSearchPath( "url", "" );
606 locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
607 }
608
609 @SuppressWarnings( "unchecked" )
610 protected List<MavenProject> getProjects()
611 {
612 List<MavenProject> projects = new ArrayList<>();
613
614
615 FilterArtifacts filter = new FilterArtifacts();
616
617 Set<Artifact> artifacts = resolveProjectArtifacts();
618 if ( this.excludeTransitive )
619 {
620 Set<Artifact> depArtifacts;
621 if ( runOnlyAtExecutionRoot )
622 {
623 depArtifacts = aggregateProjectDependencyArtifacts();
624 }
625 else
626 {
627 depArtifacts = project.getDependencyArtifacts();
628 }
629 filter.addFilter( new ProjectTransitivityFilter( depArtifacts, true ) );
630 }
631
632 filter.addFilter( new ScopeFilter( this.includeScope, this.excludeScope ) );
633 filter.addFilter( new GroupIdFilter( this.includeGroupIds, this.excludeGroupIds ) );
634 filter.addFilter( new ArtifactIdFilter( this.includeArtifactIds, this.excludeArtifactIds ) );
635
636
637 try
638 {
639 artifacts = filter.filter( artifacts );
640 }
641 catch ( ArtifactFilterException e )
642 {
643 throw new IllegalStateException( e.getMessage(), e );
644 }
645
646 for ( Artifact artifact : artifacts )
647 {
648 List<ArtifactRepository> remoteRepo = remoteArtifactRepositories;
649 if ( artifact.isSnapshot() )
650 {
651 VersionRange rng = VersionRange.createFromVersion( artifact.getBaseVersion() );
652 artifact = new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(), rng,
653 artifact.getScope(), artifact.getType(), artifact.getClassifier(),
654 artifact.getArtifactHandler(), artifact.isOptional() );
655 }
656
657 getLog().debug( "Building project for " + artifact );
658 MavenProject p;
659 try
660 {
661 ProjectBuildingRequest req = new DefaultProjectBuildingRequest()
662 .setRepositorySession( mavenSession.getRepositorySession() )
663 .setLocalRepository( localRepository )
664 .setRemoteRepositories( remoteRepo );
665 ProjectBuildingResult res = projectBuilder.build( artifact, req );
666 p = res.getProject();
667 }
668 catch ( ProjectBuildingException e )
669 {
670 getLog().warn( "Invalid project model for artifact [" + artifact.getArtifactId() + ":"
671 + artifact.getGroupId() + ":" + artifact.getVersion() + "]. "
672 + "It will be ignored by the remote resources Mojo." );
673 continue;
674 }
675
676 String supplementKey =
677 generateSupplementMapKey( p.getModel().getGroupId(), p.getModel().getArtifactId() );
678
679 if ( supplementModels.containsKey( supplementKey ) )
680 {
681 Model mergedModel = mergeModels( p.getModel(), supplementModels.get( supplementKey ) );
682 MavenProject mergedProject = new MavenProject( mergedModel );
683 projects.add( mergedProject );
684 mergedProject.setArtifact( artifact );
685 mergedProject.setVersion( artifact.getVersion() );
686 getLog().debug( "Adding project with groupId [" + mergedProject.getGroupId() + "] (supplemented)" );
687 }
688 else
689 {
690 projects.add( p );
691 getLog().debug( "Adding project with groupId [" + p.getGroupId() + "]" );
692 }
693 }
694 Collections.sort( projects, new ProjectComparator() );
695 return projects;
696 }
697
698 private Set<Artifact> resolveProjectArtifacts()
699 {
700 try
701 {
702 if ( runOnlyAtExecutionRoot )
703 {
704 List<MavenProject> projects = mavenSession.getProjects();
705 return dependencyResolver.resolve( projects, Arrays.asList( resolveScopes ), mavenSession );
706 }
707 else
708 {
709 return dependencyResolver.resolve( project, Arrays.asList( resolveScopes ), mavenSession );
710 }
711 }
712 catch ( ArtifactResolutionException 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 catch ( ArtifactNotFoundException e )
718 {
719 throw new IllegalStateException( "Failed to resolve dependencies for one or more projects in the reactor. "
720 + "Reason: " + e.getMessage(), e );
721 }
722 }
723
724 @SuppressWarnings( "unchecked" )
725 private Set<Artifact> aggregateProjectDependencyArtifacts()
726 {
727 Set<Artifact> artifacts = new LinkedHashSet<>();
728
729 List<MavenProject> projects = mavenSession.getProjects();
730 for ( MavenProject p : projects )
731 {
732 if ( p.getDependencyArtifacts() == null )
733 {
734 try
735 {
736 Set<Artifact> depArtifacts = projectArtifactFactory.createArtifacts( p );
737
738 if ( depArtifacts != null && !depArtifacts.isEmpty() )
739 {
740 for ( Artifact artifact : depArtifacts )
741 {
742 if ( artifact.getVersion() == null && artifact.getVersionRange() != null )
743 {
744
745
746
747
748 artifact.setResolvedVersion( Artifact.LATEST_VERSION );
749 }
750 artifacts.add( artifact );
751 }
752 }
753 }
754 catch ( InvalidDependencyVersionException e )
755 {
756 throw new IllegalStateException( "Failed to create dependency artifacts for: " + p.getId()
757 + ". Reason: " + e.getMessage(), e );
758 }
759 }
760 }
761
762 return artifacts;
763 }
764
765 protected Map<Organization, List<MavenProject>> getProjectsSortedByOrganization( List<MavenProject> projects )
766 {
767 Map<Organization, List<MavenProject>> organizations =
768 new TreeMap<>( new OrganizationComparator() );
769 List<MavenProject> unknownOrganization = new ArrayList<>();
770
771 for ( MavenProject p : projects )
772 {
773 if ( p.getOrganization() != null && StringUtils.isNotEmpty( p.getOrganization().getName() ) )
774 {
775 List<MavenProject> sortedProjects = organizations.get( p.getOrganization() );
776 if ( sortedProjects == null )
777 {
778 sortedProjects = new ArrayList<>();
779 }
780 sortedProjects.add( p );
781
782 organizations.put( p.getOrganization(), sortedProjects );
783 }
784 else
785 {
786 unknownOrganization.add( p );
787 }
788 }
789 if ( !unknownOrganization.isEmpty() )
790 {
791 Organization unknownOrg = new Organization();
792 unknownOrg.setName( "an unknown organization" );
793 organizations.put( unknownOrg, unknownOrganization );
794 }
795
796 return organizations;
797 }
798
799 protected boolean copyResourceIfExists( File file, String relFileName, VelocityContext context )
800 throws IOException, MojoExecutionException
801 {
802 for ( Resource resource : resources )
803 {
804 File resourceDirectory = new File( resource.getDirectory() );
805
806 if ( !resourceDirectory.exists() )
807 {
808 continue;
809 }
810
811
812 File source = new File( resourceDirectory, relFileName );
813 File templateSource = new File( resourceDirectory, relFileName + TEMPLATE_SUFFIX );
814
815 if ( !source.exists() && templateSource.exists() )
816 {
817 source = templateSource;
818 }
819
820 if ( source.exists() && !source.equals( file ) )
821 {
822 if ( source == templateSource )
823 {
824 try ( DeferredFileOutputStream os =
825 new DeferredFileOutputStream( velocityFilterInMemoryThreshold, file ) )
826 {
827 try ( Reader reader = getReader( source ); Writer writer = getWriter( os ) )
828 {
829 velocity.evaluate( context, writer, "", reader );
830 }
831 catch ( ParseErrorException | MethodInvocationException | ResourceNotFoundException e )
832 {
833 throw new MojoExecutionException( "Error rendering velocity resource: " + source, e );
834 }
835 fileWriteIfDiffers( os );
836 }
837 }
838 else if ( resource.isFiltering() )
839 {
840
841 MavenFileFilterRequest req = setupRequest( resource, source, file );
842
843 try
844 {
845 fileFilter.copyFile( req );
846 }
847 catch ( MavenFilteringException e )
848 {
849 throw new MojoExecutionException( "Error filtering resource: " + source, e );
850 }
851 }
852 else
853 {
854 FileUtils.copyFile( source, file );
855 }
856
857
858 resource.addExclude( relFileName );
859
860 return true;
861 }
862
863 }
864 return false;
865 }
866
867 private Reader getReader( File source ) throws IOException
868 {
869 if ( encoding != null )
870 {
871 return new InputStreamReader( new FileInputStream( source ), encoding );
872 }
873 else
874 {
875 return ReaderFactory.newPlatformReader( source );
876 }
877 }
878
879 private Writer getWriter( OutputStream os ) throws IOException
880 {
881 if ( encoding != null )
882 {
883 return new OutputStreamWriter( os, encoding );
884 }
885 else
886 {
887 return WriterFactory.newPlatformWriter( os );
888 }
889 }
890
891
892
893
894
895
896
897
898
899
900
901
902 private void fileWriteIfDiffers( DeferredFileOutputStream outStream )
903 throws IOException
904 {
905 File file = outStream.getFile();
906 if ( outStream.isThresholdExceeded() )
907 {
908 getLog().info( "File " + file + " was overwritten due to content limit threshold "
909 + outStream.getThreshold() + " reached" );
910 return;
911 }
912 boolean needOverwrite = true;
913
914 if ( file.exists() )
915 {
916 try ( InputStream is = new FileInputStream( file ) )
917 {
918 final InputStream newContents = new ByteArrayInputStream( outStream.getData() );
919 needOverwrite = !IOUtil.contentEquals( is, newContents );
920 if ( getLog().isDebugEnabled() )
921 {
922 getLog().debug( "File " + file + " contents "
923 + ( needOverwrite ? "differs" : "does not differ" ) );
924 }
925 }
926 }
927
928 if ( !needOverwrite )
929 {
930 getLog().debug( "File " + file + " is up to date" );
931 return;
932 }
933 getLog().debug( "Writing " + file );
934
935 try ( OutputStream os = new FileOutputStream( file ) )
936 {
937 outStream.writeTo( os );
938 }
939 }
940
941 private MavenFileFilterRequest setupRequest( Resource resource, File source, File file )
942 {
943 MavenFileFilterRequest req = new MavenFileFilterRequest();
944 req.setFrom( source );
945 req.setTo( file );
946 req.setFiltering( resource.isFiltering() );
947
948 req.setMavenProject( project );
949 req.setMavenSession( mavenSession );
950 req.setInjectProjectBuildFilters( true );
951
952 if ( encoding != null )
953 {
954 req.setEncoding( encoding );
955 }
956
957 if ( filterDelimiters != null && !filterDelimiters.isEmpty() )
958 {
959 LinkedHashSet<String> delims = new LinkedHashSet<>();
960 if ( useDefaultFilterDelimiters )
961 {
962 delims.addAll( req.getDelimiters() );
963 }
964
965 for ( String delim : filterDelimiters )
966 {
967 if ( delim == null )
968 {
969 delims.add( "${*}" );
970 }
971 else
972 {
973 delims.add( delim );
974 }
975 }
976
977 req.setDelimiters( delims );
978 }
979
980 return req;
981 }
982
983 protected void validate()
984 throws MojoExecutionException
985 {
986 int bundleCount = 1;
987
988 for ( String artifactDescriptor : resourceBundles )
989 {
990
991
992 String[] s = StringUtils.split( artifactDescriptor, ":" );
993
994 if ( s.length < 3 || s.length > 5 )
995 {
996 String position;
997
998 if ( bundleCount == 1 )
999 {
1000 position = "1st";
1001 }
1002 else if ( bundleCount == 2 )
1003 {
1004 position = "2nd";
1005 }
1006 else if ( bundleCount == 3 )
1007 {
1008 position = "3rd";
1009 }
1010 else
1011 {
1012 position = bundleCount + "th";
1013 }
1014
1015 throw new MojoExecutionException( "The " + position
1016 + " resource bundle configured must specify a groupId, artifactId, "
1017 + " version and, optionally, type and classifier for a remote resource bundle. "
1018 + "Must be of the form <resourceBundle>groupId:artifactId:version</resourceBundle>, "
1019 + "<resourceBundle>groupId:artifactId:version:type</resourceBundle> or "
1020 + "<resourceBundle>groupId:artifactId:version:type:classifier</resourceBundle>" );
1021 }
1022
1023 bundleCount++;
1024 }
1025
1026 }
1027
1028 private static final String KEY_PROJECTS = "projects";
1029 private static final String KEY_PROJECTS_ORGS = "projectsSortedByOrganization";
1030
1031 protected VelocityContext buildVelocityContext( Map<String, Object> properties )
1032 throws MojoExecutionException
1033 {
1034
1035 VelocityContext context = new VelocityContext( properties )
1036 {
1037 @Override
1038 public Object internalGet( String key )
1039 {
1040 Object result = super.internalGet( key );
1041 if ( result == null && key != null && key.startsWith( KEY_PROJECTS ) && containsKey( key ) )
1042 {
1043
1044 List<MavenProject> projects = getProjects();
1045 put( KEY_PROJECTS, projects );
1046 put( KEY_PROJECTS_ORGS, getProjectsSortedByOrganization( projects ) );
1047 return super.internalGet( key );
1048 }
1049 return result;
1050 }
1051 };
1052
1053 context.put( KEY_PROJECTS, null );
1054 context.put( KEY_PROJECTS_ORGS, null );
1055
1056
1057
1058 MavenArchiver archiver = new MavenArchiver();
1059 Date outputDate = archiver.parseOutputTimestamp( outputTimestamp );
1060
1061 String inceptionYear = project.getInceptionYear();
1062 String year = new SimpleDateFormat( "yyyy" ).format( ( outputDate == null ) ? new Date() : outputDate );
1063
1064 if ( StringUtils.isEmpty( inceptionYear ) )
1065 {
1066 if ( getLog().isDebugEnabled() )
1067 {
1068 getLog().debug( "inceptionYear not specified, defaulting to " + year );
1069 }
1070
1071 inceptionYear = year;
1072 }
1073 context.put( "project", project );
1074 context.put( "presentYear", year );
1075 context.put( "locator", locator );
1076
1077 if ( inceptionYear.equals( year ) )
1078 {
1079 context.put( "projectTimespan", year );
1080 }
1081 else
1082 {
1083 context.put( "projectTimespan", inceptionYear + "-" + year );
1084 }
1085 return context;
1086 }
1087
1088 private List<File> downloadBundles( List<String> bundles )
1089 throws MojoExecutionException
1090 {
1091 List<File> bundleArtifacts = new ArrayList<>();
1092
1093 for ( String artifactDescriptor : bundles )
1094 {
1095 getLog().info( "Preparing remote bundle " + artifactDescriptor );
1096
1097 String[] s = artifactDescriptor.split( ":" );
1098
1099 File artifactFile = null;
1100
1101 if ( mavenSession != null )
1102 {
1103 List<MavenProject> list = mavenSession.getProjects();
1104 for ( MavenProject p : list )
1105 {
1106 if ( s[0].equals( p.getGroupId() ) && s[1].equals( p.getArtifactId() )
1107 && s[2].equals( p.getVersion() ) )
1108 {
1109 if ( s.length >= 4 && "test-jar".equals( s[3] ) )
1110 {
1111 artifactFile = new File( p.getBuild().getTestOutputDirectory() );
1112 }
1113 else
1114 {
1115 artifactFile = new File( p.getBuild().getOutputDirectory() );
1116 }
1117 }
1118 }
1119 }
1120 if ( artifactFile == null || !artifactFile.exists() )
1121 {
1122 String type = ( s.length >= 4 ? s[3] : "jar" );
1123 String classifier = ( s.length == 5 ? s[4] : null );
1124 Artifact artifact = repositorySystem.createArtifactWithClassifier( s[0], s[1], s[2], type, classifier );
1125
1126 try
1127 {
1128 ArtifactResult result = artifactResolver.resolveArtifact(
1129 mavenSession.getProjectBuildingRequest(), artifact );
1130 artifactFile = result.getArtifact().getFile();
1131 }
1132 catch ( ArtifactResolverException e )
1133 {
1134 throw new MojoExecutionException( "Error processing remote resources", e );
1135 }
1136
1137 }
1138 bundleArtifacts.add( artifactFile );
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 }