1 package org.codehaus.mojo.webstart;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.commons.collections.MapUtils;
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.maven.artifact.repository.ArtifactRepository;
25 import org.apache.maven.plugin.AbstractMojo;
26 import org.apache.maven.plugin.MojoExecutionException;
27 import org.apache.maven.plugins.annotations.Component;
28 import org.apache.maven.plugins.annotations.Parameter;
29 import org.apache.maven.project.MavenProject;
30 import org.codehaus.mojo.webstart.dependency.filenaming.DependencyFilenameStrategy;
31 import org.codehaus.mojo.webstart.pack200.Pack200Config;
32 import org.codehaus.mojo.webstart.pack200.Pack200Tool;
33 import org.codehaus.mojo.webstart.sign.SignConfig;
34 import org.codehaus.mojo.webstart.sign.SignTool;
35 import org.codehaus.mojo.webstart.util.ArtifactUtil;
36 import org.codehaus.mojo.webstart.util.IOUtil;
37 import org.codehaus.mojo.webstart.util.JarUtil;
38 import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
39
40 import java.io.File;
41 import java.io.FileFilter;
42 import java.io.IOException;
43 import java.net.MalformedURLException;
44 import java.net.URL;
45 import java.net.URLClassLoader;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Map;
49
50
51
52
53
54
55
56
57
58 public abstract class AbstractBaseJnlpMojo
59 extends AbstractMojo
60 {
61
62
63
64
65 private static final String DEFAULT_RESOURCES_DIR = "src/main/jnlp/resources";
66
67
68
69
70 private static final String UNPROCESSED_PREFIX = "unprocessed_";
71
72
73
74
75 public static final String JAR_SUFFIX = ".jar";
76
77
78
79
80
81
82
83
84 @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
85 private ArtifactRepository localRepository;
86
87
88
89
90 @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
91 private List<ArtifactRepository> remoteRepositories;
92
93
94
95
96 @Parameter( property = "jnlp.workDirectory", defaultValue = "${project.build.directory}/jnlp", required = true )
97 private File workDirectory;
98
99
100
101
102 @Parameter( property = "jnlp.libPath", defaultValue = "" )
103 protected String libPath;
104
105
106
107
108
109 @Parameter( property = "jnlp.resourcesDirectory" )
110 private File resourcesDirectory;
111
112
113
114
115 @Parameter( property = "jnlp.templateDirectory", defaultValue = "${project.basedir}/src/main/jnlp", required = true )
116 private File templateDirectory;
117
118
119
120
121
122
123 @Parameter
124 private Pack200Config pack200;
125
126
127
128
129 @Parameter
130 private SignConfig sign;
131
132
133
134
135
136 @Parameter( property = "jnlp.gzip", defaultValue = "false" )
137 private boolean gzip;
138
139
140
141
142 @Parameter( property = "webstart.verbose", alias = "verbose", defaultValue = "false" )
143 private boolean verbose;
144
145
146
147
148
149
150 @Parameter( property = "jnlp.excludeTransitive" )
151 private boolean excludeTransitive;
152
153
154
155
156
157
158 @Parameter( property = "jnlp.codebase", defaultValue = "${project.url}/jnlp" )
159 private String codebase;
160
161
162
163
164
165
166
167
168 @Parameter( property = "jnlp.encoding", defaultValue = "${project.build.sourceEncoding}" )
169 private String encoding;
170
171
172
173
174 @Parameter( property = "jnlp.unsign", alias = "unsign", defaultValue = "false" )
175 private boolean unsignAlreadySignedJars;
176
177
178
179
180
181
182
183
184
185 @Parameter( property = "jnlp.canUnsign", defaultValue = "true" )
186 private boolean canUnsign;
187
188
189
190
191
192
193
194
195
196
197
198
199
200 @Parameter
201 private Map<String, String> updateManifestEntries;
202
203
204
205
206
207
208
209 @Parameter( defaultValue = "${project.compileClasspathElements}", required = true, readonly = true )
210 private List<?> compileClassPath;
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227 @Parameter( property = "jnlp.filenameMapping", defaultValue = "simple", required = true)
228 private String filenameMapping;
229
230
231
232
233
234
235 @Parameter( property = "jnlp.useUniqueVersions", defaultValue = "false" )
236 private boolean useUniqueVersions;
237
238
239
240
241
242
243
244
245 @Component
246 private SignTool signTool;
247
248
249
250
251
252
253
254
255
256
257
258 @Component( role = Pack200Tool.class )
259 private Pack200Tool pack200Tool;
260
261
262
263
264
265
266 @Component
267 private ArtifactUtil artifactUtil;
268
269
270
271
272
273
274 @Component
275 private IOUtil ioUtil;
276
277
278
279
280
281
282 @Component( hint = "default" )
283 private JarUtil jarUtil;
284
285
286
287
288
289
290 @Component( role = DependencyFilenameStrategy.class )
291 private Map<String, DependencyFilenameStrategy> dependencyFilenameStrategyMap;
292
293
294
295
296 @Component(hint = "mng-4384")
297 private SecDispatcher securityDispatcher;
298
299
300
301
302
303
304
305
306 private final List<String> modifiedJnlpArtifacts = new ArrayList<String>();
307
308
309
310
311
312
313 private final FileFilter unprocessedJarFileFilter;
314
315
316
317
318 private final FileFilter processedJarFileFilter;
319
320
321
322
323 private final FileFilter unprocessedPack200FileFilter;
324
325
326
327
328 private DependencyFilenameStrategy dependencyFilenameStrategy;
329
330
331
332
333 public AbstractBaseJnlpMojo()
334 {
335
336 processedJarFileFilter = new FileFilter()
337 {
338
339
340
341 public boolean accept( File pathname )
342 {
343 return pathname.isFile() && pathname.getName().endsWith( JAR_SUFFIX ) &&
344 !pathname.getName().startsWith( UNPROCESSED_PREFIX );
345 }
346 };
347
348 unprocessedJarFileFilter = new FileFilter()
349 {
350
351
352
353 public boolean accept( File pathname )
354 {
355 return pathname.isFile() && pathname.getName().startsWith( UNPROCESSED_PREFIX ) &&
356 pathname.getName().endsWith( JAR_SUFFIX );
357 }
358 };
359
360 unprocessedPack200FileFilter = new FileFilter()
361 {
362
363
364
365 public boolean accept( File pathname )
366 {
367 return pathname.isFile() && pathname.getName().startsWith( UNPROCESSED_PREFIX ) &&
368 ( pathname.getName().endsWith( JAR_SUFFIX + Pack200Tool.PACK_GZ_EXTENSION ) ||
369 pathname.getName().endsWith( JAR_SUFFIX + Pack200Tool.PACK_EXTENSION ) );
370 }
371 };
372 }
373
374
375
376
377
378 public abstract MavenProject getProject();
379
380
381
382
383
384
385
386 public String getLibPath()
387 {
388 if ( StringUtils.isBlank( libPath ) )
389 {
390 return null;
391 }
392 return libPath;
393 }
394
395
396
397
398
399
400
401 public boolean isPack200()
402 {
403 return pack200 != null && pack200.isEnabled();
404 }
405
406
407
408
409
410
411 public List<String> getPack200PassFiles()
412 {
413 return pack200 == null ? null : pack200.getPassFiles();
414 }
415
416
417
418
419
420
421
422
423
424
425
426 protected File getWorkDirectory()
427 {
428 return workDirectory;
429 }
430
431
432
433
434
435
436 protected File getLibDirectory()
437 {
438 if ( getLibPath() != null )
439 {
440 return new File( getWorkDirectory(), getLibPath() );
441 }
442 return getWorkDirectory();
443 }
444
445
446
447
448
449
450
451 protected File getResourcesDirectory()
452 {
453
454 if ( resourcesDirectory == null )
455 {
456 resourcesDirectory = new File( getProject().getBasedir(), DEFAULT_RESOURCES_DIR );
457 }
458
459 return resourcesDirectory;
460
461 }
462
463
464
465
466
467
468
469 protected File getTemplateDirectory()
470 {
471 return templateDirectory;
472 }
473
474
475
476
477
478
479 protected ArtifactRepository getLocalRepository()
480 {
481 return localRepository;
482 }
483
484
485
486
487
488
489
490 protected List<ArtifactRepository> getRemoteRepositories()
491 {
492 return remoteRepositories;
493 }
494
495
496
497
498
499
500 protected SignConfig getSign()
501 {
502 return sign;
503 }
504
505
506
507
508
509
510 protected String getCodebase()
511 {
512 return codebase;
513 }
514
515
516
517
518
519
520
521 protected boolean isGzip()
522 {
523 return gzip;
524 }
525
526
527
528
529
530
531 protected boolean isVerbose()
532 {
533 return verbose;
534 }
535
536
537
538
539
540
541
542 protected boolean isExcludeTransitive()
543 {
544 return this.excludeTransitive;
545 }
546
547
548
549
550
551
552
553 protected List<String> getModifiedJnlpArtifacts()
554 {
555 return modifiedJnlpArtifacts;
556 }
557
558
559
560
561 protected String getEncoding()
562 {
563 if ( StringUtils.isEmpty( encoding ) )
564 {
565 encoding = "utf-8";
566 getLog().warn( "No encoding defined, will use the default one : " + encoding );
567 }
568 return encoding;
569 }
570
571 protected DependencyFilenameStrategy getDependencyFilenameStrategy()
572 {
573 if ( dependencyFilenameStrategy == null )
574 {
575 dependencyFilenameStrategy = dependencyFilenameStrategyMap.get(filenameMapping );
576 }
577 return dependencyFilenameStrategy;
578 }
579
580 protected boolean isUseUniqueVersions()
581 {
582 return useUniqueVersions;
583 }
584
585 protected void checkDependencyFilenameStrategy()
586 throws MojoExecutionException
587 {
588 if ( getDependencyFilenameStrategy() == null )
589 {
590
591 dependencyFilenameStrategy = dependencyFilenameStrategyMap.get(filenameMapping );
592 if (dependencyFilenameStrategy==null) {
593 throw new MojoExecutionException( "Could not find filenameMapping named '"+filenameMapping+"', use one of the following one: "+ dependencyFilenameStrategyMap.keySet());
594 }
595 }
596 }
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614 protected boolean copyJarAsUnprocessedToDirectoryIfNecessary( File sourceFile, File targetDirectory,
615 String targetFilename )
616 throws MojoExecutionException
617 {
618
619 if ( sourceFile == null )
620 {
621 throw new IllegalArgumentException( "sourceFile is null" );
622 }
623
624 if ( targetFilename == null )
625 {
626 targetFilename = sourceFile.getName();
627 }
628
629 File signedTargetFile = new File( targetDirectory, targetFilename );
630
631 File unsignedTargetFile = toUnprocessFile( targetDirectory, targetFilename );
632
633 boolean shouldCopy =
634 !signedTargetFile.exists() || ( signedTargetFile.lastModified() < sourceFile.lastModified() );
635
636 shouldCopy &=
637 ( !unsignedTargetFile.exists() || ( unsignedTargetFile.lastModified() < sourceFile.lastModified() ) );
638
639 if ( shouldCopy )
640 {
641 getIoUtil().copyFile( sourceFile, unsignedTargetFile );
642
643 }
644 else
645 {
646 getLog().debug(
647 "Source file hasn't changed. Do not reprocess " + signedTargetFile + " with " + sourceFile + "." );
648 }
649
650 return shouldCopy;
651 }
652
653
654
655
656
657
658
659 protected void signOrRenameJars()
660 throws MojoExecutionException
661 {
662
663 if ( sign != null )
664 {
665 try
666 {
667 ClassLoader loader = getCompileClassLoader();
668 sign.init( getWorkDirectory(), getLog().isDebugEnabled(), signTool, securityDispatcher, loader );
669 }
670 catch ( MalformedURLException e )
671 {
672 throw new MojoExecutionException( "Could not create classloader", e );
673 }
674
675 if ( unsignAlreadySignedJars )
676 {
677 removeExistingSignatures( getLibDirectory() );
678 }
679
680 if ( isPack200() )
681 {
682
683
684
685
686
687 unpackJars( getLibDirectory() );
688
689
690
691 ioUtil.deleteFiles( getLibDirectory(), unprocessedPack200FileFilter );
692
693
694 }
695
696 if ( MapUtils.isNotEmpty( updateManifestEntries ) )
697 {
698 updateManifestEntries( getLibDirectory() );
699 }
700
701 int signedJars = signJars( getLibDirectory() );
702
703 if ( signedJars != getModifiedJnlpArtifacts().size() )
704 {
705 throw new IllegalStateException(
706 "The number of signed artifacts (" + signedJars + ") differ from the number of modified " +
707 "artifacts (" + getModifiedJnlpArtifacts().size() + "). Implementation error" );
708 }
709
710 }
711 else
712 {
713 makeUnprocessedFilesFinal( getLibDirectory() );
714 }
715
716 if ( isPack200() )
717 {
718 verboseLog( "-- Pack jars" );
719 pack200Jars( getLibDirectory(), processedJarFileFilter );
720 }
721 }
722
723
724 protected void pack200Jars( File directory, FileFilter filter )
725 throws MojoExecutionException
726 {
727 try
728 {
729 getPack200Tool().packJars( directory, filter, isGzip(), getPack200PassFiles() );
730 }
731 catch ( IOException e )
732 {
733 throw new MojoExecutionException( "Could not pack200 jars: ", e );
734 }
735 }
736
737 protected URL findDefaultTemplateURL(JnlpFileType fileType)
738 {
739 return getClass().getClassLoader().getResource( fileType.getDefaultTemplateName() );
740 }
741
742
743
744
745 protected String getWebstartJarURLForVelocity()
746 {
747 String url = findDefaultTemplateURL(JnlpFileType.application).toString();
748 return url.substring( 0, url.indexOf( "!" ) + 2 );
749 }
750
751 protected boolean isJarSigned( File jarFile )
752 throws MojoExecutionException
753 {
754
755 return signTool.isJarSigned( jarFile );
756 }
757
758 protected ArtifactUtil getArtifactUtil()
759 {
760 return artifactUtil;
761 }
762
763 protected IOUtil getIoUtil()
764 {
765 return ioUtil;
766 }
767
768 protected Pack200Tool getPack200Tool()
769 {
770 return pack200Tool;
771 }
772
773
774
775
776
777
778 protected void verboseLog( String msg )
779 {
780 if ( isVerbose() )
781 {
782 getLog().info( msg );
783 }
784 else
785 {
786 getLog().debug( msg );
787 }
788 }
789
790
791
792
793
794 private void unpackJars( File directory )
795 throws MojoExecutionException
796 {
797 getLog().info( "-- Unpack jars before sign operation " );
798
799 verboseLog(
800 "see http://docs.oracle.com/javase/7/docs/technotes/guides/deployment/deployment-guide/pack200.html" );
801
802
803 pack200Jars( directory, unprocessedJarFileFilter );
804
805
806 try
807 {
808 getPack200Tool().unpackJars( directory, unprocessedPack200FileFilter );
809 }
810 catch ( IOException e )
811 {
812 throw new MojoExecutionException( "Could not unpack200 jars: ", e );
813 }
814 }
815
816 private int makeUnprocessedFilesFinal( File directory )
817 throws MojoExecutionException
818 {
819 File[] jarFiles = directory.listFiles( unprocessedJarFileFilter );
820
821 getLog().debug(
822 "makeUnprocessedFilesFinal in " + directory + " found " + jarFiles.length + " file(s) to rename" );
823
824 if ( jarFiles.length == 0 )
825 {
826 return 0;
827 }
828
829 for ( File unprocessedJarFile : jarFiles )
830 {
831
832 File finalJar = toProcessFile( unprocessedJarFile );
833
834 ioUtil.deleteFile( finalJar );
835
836 ioUtil.renameTo( unprocessedJarFile, finalJar );
837 }
838 return jarFiles.length;
839 }
840
841
842
843
844
845 private void updateManifestEntries( File directory )
846 throws MojoExecutionException
847 {
848
849 File[] jarFiles = directory.listFiles( unprocessedJarFileFilter );
850
851 getLog().info( "-- Update manifest entries" );
852 getLog().debug( "updateManifestEntries in " + directory + " found " + jarFiles.length + " jar(s) to treat" );
853
854 if ( jarFiles.length == 0 )
855 {
856 return;
857 }
858
859 for ( File unprocessedJarFile : jarFiles )
860 {
861 verboseLog( "Update manifest " + toProcessFile( unprocessedJarFile ).getName() );
862
863 jarUtil.updateManifestEntries( unprocessedJarFile, updateManifestEntries );
864 }
865 }
866
867
868
869
870
871
872 private int signJars( File directory )
873 throws MojoExecutionException
874 {
875
876 File[] jarFiles = directory.listFiles( unprocessedJarFileFilter );
877
878 getLog().info( "-- Sign jars" );
879 getLog().debug( "signJars in " + directory + " found " + jarFiles.length + " jar(s) to sign" );
880
881 if ( jarFiles.length == 0 )
882 {
883 return 0;
884 }
885
886 boolean signVerify = sign.isVerify();
887
888 for ( File unprocessedJarFile : jarFiles )
889 {
890
891 File signedJar = toProcessFile( unprocessedJarFile );
892 ioUtil.deleteFile( signedJar );
893
894 verboseLog( "Sign " + signedJar.getName() );
895 signTool.sign( sign, unprocessedJarFile, signedJar );
896
897 getLog().debug( "lastModified signedJar:" + signedJar.lastModified() + " unprocessed signed Jar:" +
898 unprocessedJarFile.lastModified() );
899
900 if ( signVerify )
901 {
902 verboseLog( "Verify signature of " + signedJar.getName() );
903 signTool.verify( sign, signedJar, isVerbose() );
904 }
905
906
907 ioUtil.deleteFile( unprocessedJarFile );
908 }
909
910 return jarFiles.length;
911 }
912
913
914
915
916
917
918
919
920
921 private int removeExistingSignatures( File workDirectory )
922 throws MojoExecutionException
923 {
924 getLog().info( "-- Remove existing signatures" );
925
926
927 File tempDir = new File( workDirectory, "temp_extracted_jars" );
928 ioUtil.removeDirectory( tempDir );
929
930
931 ioUtil.makeDirectoryIfNecessary( tempDir );
932
933
934 File[] jarFiles = workDirectory.listFiles( unprocessedJarFileFilter );
935
936 for ( File jarFile : jarFiles )
937 {
938 if ( isJarSigned( jarFile ) )
939 {
940 if ( !canUnsign )
941 {
942 throw new MojoExecutionException(
943 "neverUnsignAlreadySignedJar is set to true and a jar file [" + jarFile +
944 " was asked to be unsign,\n please prefer use in this case an extension for " +
945 "signed jars or not set to true the neverUnsignAlreadySignedJar parameter, Make " +
946 "your choice:)" );
947 }
948 verboseLog( "Remove signature " + toProcessFile( jarFile ).getName() );
949
950 signTool.unsign( jarFile, isVerbose() );
951 }
952 else
953 {
954 verboseLog( "Skip not signed " + toProcessFile( jarFile ).getName() );
955 }
956 }
957
958
959 ioUtil.removeDirectory( tempDir );
960
961 return jarFiles.length;
962 }
963
964 private ClassLoader getCompileClassLoader()
965 throws MalformedURLException
966 {
967 URL[] urls = new URL[compileClassPath.size()];
968 for ( int i = 0; i < urls.length; i++ )
969 {
970 String spec = compileClassPath.get( i ).toString();
971 URL url = new File( spec ).toURI().toURL();
972 urls[i] = url;
973 }
974 return new URLClassLoader( urls );
975 }
976
977 private File toUnprocessFile( File targetDirectory, String sourceName )
978 {
979 if ( sourceName.startsWith( UNPROCESSED_PREFIX ) )
980 {
981 throw new IllegalStateException( sourceName + " does start with " + UNPROCESSED_PREFIX );
982 }
983 String targetFilename = UNPROCESSED_PREFIX + sourceName;
984 return new File( targetDirectory, targetFilename );
985 }
986
987 private File toProcessFile( File source )
988 {
989 if ( !source.getName().startsWith( UNPROCESSED_PREFIX ) )
990 {
991 throw new IllegalStateException( source.getName() + " does not start with " + UNPROCESSED_PREFIX );
992 }
993 String targetFilename = source.getName().substring( UNPROCESSED_PREFIX.length() );
994 return new File( source.getParentFile(), targetFilename );
995 }
996
997 }