1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.codehaus.mojo.nbm;
18
19 import java.io.BufferedReader;
20 import java.io.ByteArrayInputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStreamWriter;
28 import java.io.Reader;
29 import java.io.Writer;
30 import java.net.URL;
31 import java.util.Properties;
32 import java.util.StringTokenizer;
33 import java.util.jar.JarFile;
34 import org.apache.maven.plugin.MojoExecutionException;
35 import org.apache.maven.plugin.MojoFailureException;
36 import org.apache.maven.plugins.annotations.Component;
37 import org.apache.maven.plugins.annotations.LifecyclePhase;
38 import org.apache.maven.plugins.annotations.Mojo;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.project.MavenProjectHelper;
41 import org.apache.tools.ant.Project;
42 import org.apache.tools.ant.taskdefs.GenerateKey;
43 import org.apache.tools.ant.taskdefs.SignJar;
44 import org.apache.tools.ant.taskdefs.Taskdef;
45 import org.apache.tools.ant.types.FileSet;
46 import org.apache.tools.ant.types.Parameter;
47 import org.apache.tools.ant.types.selectors.AndSelector;
48 import org.apache.tools.ant.types.selectors.FilenameSelector;
49 import org.apache.tools.ant.types.selectors.OrSelector;
50 import org.codehaus.plexus.archiver.zip.ZipArchiver;
51 import org.codehaus.plexus.components.io.resources.PlexusIoResource;
52 import org.codehaus.plexus.util.DirectoryScanner;
53 import org.codehaus.plexus.util.FileUtils;
54 import org.codehaus.plexus.util.IOUtil;
55 import org.codehaus.plexus.util.InterpolationFilterReader;
56 import org.netbeans.nbbuild.MakeJNLP;
57 import org.netbeans.nbbuild.ModuleSelector;
58 import org.netbeans.nbbuild.VerifyJNLP;
59
60
61
62
63
64
65
66 @Mojo(name="webstart-app", defaultPhase= LifecyclePhase.PACKAGE )
67 public class CreateWebstartAppMojo
68 extends AbstractNbmMojo
69 {
70
71
72
73
74
75 @org.apache.maven.plugins.annotations.Parameter(required=true, readonly=true, property="project")
76 private MavenProject project;
77
78 @Component
79 protected MavenProjectHelper projectHelper;
80
81
82
83
84 @org.apache.maven.plugins.annotations.Parameter(required=true, property="netbeans.branding.token")
85 protected String brandingToken;
86
87
88
89
90 @org.apache.maven.plugins.annotations.Parameter(required=true, defaultValue="${project.build.directory}")
91 private File outputDirectory;
92
93
94
95
96
97 @org.apache.maven.plugins.annotations.Parameter(required=true, defaultValue="${project.build.directory}/${project.artifactId}-${project.version}-jnlp.war")
98 private File destinationFile;
99
100
101
102
103
104 @org.apache.maven.plugins.annotations.Parameter(defaultValue="webstart", property="nbm.webstart.classifier")
105 private String webstartClassifier;
106
107
108
109
110
111 @org.apache.maven.plugins.annotations.Parameter(property="nbm.webstart.codebase")
112 private String codebase;
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 @org.apache.maven.plugins.annotations.Parameter
131 private File masterJnlpFile;
132
133
134
135
136
137
138
139 @org.apache.maven.plugins.annotations.Parameter(property="master.jnlp.file.name")
140 private String masterJnlpFileName;
141
142
143
144
145 @org.apache.maven.plugins.annotations.Parameter(property="keystore")
146 private String keystore;
147
148
149
150
151 @org.apache.maven.plugins.annotations.Parameter(property="keystorepass")
152 private String keystorepassword;
153
154
155
156
157 @org.apache.maven.plugins.annotations.Parameter(property="keystorealias")
158 private String keystorealias;
159
160
161
162
163
164 @org.apache.maven.plugins.annotations.Parameter(property="keystoretype")
165 private String keystoretype;
166
167
168
169
170
171
172
173
174
175
176 @org.apache.maven.plugins.annotations.Parameter(defaultValue="false", property="nbm.webstart.versions")
177 private boolean processJarVersions;
178
179
180
181
182
183 @org.apache.maven.plugins.annotations.Parameter(property="netbeans.run.params")
184 private String additionalArguments;
185
186
187
188
189
190
191 @Override
192 public void execute()
193 throws MojoExecutionException, MojoFailureException
194 {
195 if ( !"nbm-application".equals( project.getPackaging() ) )
196 {
197 throw new MojoExecutionException(
198 "This goal only makes sense on project with nbm-application packaging." );
199 }
200 Project antProject = antProject();
201
202 getLog().warn( "WARNING: Unsigned and self-signed WebStart applications are deprecated from JDK7u21 onwards. To ensure future correct functionality please use trusted certificate.");
203
204 if ( keystore != null && keystorealias != null && keystorepassword != null )
205 {
206 File ks = new File( keystore );
207 if ( !ks.exists() )
208 {
209 throw new MojoFailureException( "Cannot find keystore file at " + ks.getAbsolutePath() );
210 }
211 else
212 {
213
214 }
215 }
216 else if ( keystore != null || keystorepassword != null || keystorealias != null )
217 {
218 throw new MojoFailureException(
219 "If you want to sign the jnlp application, you need to define all three keystore related parameters." );
220 }
221 else
222 {
223 File generatedKeystore = new File( outputDirectory, "generated.keystore" );
224 if ( ! generatedKeystore.exists() )
225 {
226 getLog().warn( "Keystore related parameters not set, generating a default keystore." );
227 GenerateKey genTask = (GenerateKey) antProject.createTask( "genkey" );
228 genTask.setAlias( "jnlp" );
229 genTask.setStorepass( "netbeans" );
230 genTask.setDname( "CN=" + System.getProperty( "user.name" ) );
231 genTask.setKeystore( generatedKeystore.getAbsolutePath() );
232 genTask.execute();
233 }
234 keystore = generatedKeystore.getAbsolutePath();
235 keystorepassword = "netbeans";
236 keystorealias = "jnlp";
237 }
238
239 Taskdef taskdef = (Taskdef) antProject.createTask( "taskdef" );
240 taskdef.setClassname( "org.netbeans.nbbuild.MakeJNLP" );
241 taskdef.setName( "makejnlp" );
242 taskdef.execute();
243
244 taskdef = (Taskdef) antProject.createTask( "taskdef" );
245 taskdef.setClassname( "org.netbeans.nbbuild.VerifyJNLP" );
246 taskdef.setName( "verifyjnlp" );
247 taskdef.execute();
248
249
250 try
251 {
252 File webstartBuildDir = new File(
253 outputDirectory + File.separator + "webstart" + File.separator + brandingToken );
254 if ( webstartBuildDir.exists() )
255 {
256 FileUtils.deleteDirectory( webstartBuildDir );
257 }
258 webstartBuildDir.mkdirs();
259 final String localCodebase = codebase != null ? codebase : webstartBuildDir.toURI().toString();
260 getLog().info( "Generating webstartable binaries at " + webstartBuildDir.getAbsolutePath() );
261
262 File nbmBuildDirFile = new File( outputDirectory, brandingToken );
263
264
265
266 MakeJNLP jnlpTask = (MakeJNLP) antProject.createTask( "makejnlp" );
267 jnlpTask.setDir( webstartBuildDir );
268 jnlpTask.setCodebase( localCodebase );
269
270 jnlpTask.setVerify( false );
271 jnlpTask.setPermissions( "<security><all-permissions/></security>" );
272 jnlpTask.setSignJars( true );
273
274 jnlpTask.setAlias( keystorealias );
275 jnlpTask.setKeystore( keystore );
276 jnlpTask.setStorePass( keystorepassword );
277 if ( keystoretype != null )
278 {
279 jnlpTask.setStoreType( keystoretype );
280 }
281 jnlpTask.setProcessJarVersions( processJarVersions );
282
283 FileSet fs = jnlpTask.createModules();
284 fs.setDir( nbmBuildDirFile );
285 OrSelector or = new OrSelector();
286 AndSelector and = new AndSelector();
287 FilenameSelector inc = new FilenameSelector();
288 inc.setName( "*/modules/**/*.jar" );
289 or.addFilename( inc );
290 inc = new FilenameSelector();
291 inc.setName( "*/lib/**/*.jar" );
292 or.addFilename( inc );
293 inc = new FilenameSelector();
294 inc.setName( "*/core/**/*.jar" );
295 or.addFilename( inc );
296
297 ModuleSelector ms = new ModuleSelector();
298 Parameter included = new Parameter();
299 included.setName( "includeClusters" );
300 included.setValue( "" );
301 Parameter excluded = new Parameter();
302 excluded.setName( "excludeClusters" );
303 excluded.setValue( "" );
304 Parameter exModules = new Parameter();
305 exModules.setName( "excludeModules" );
306 exModules.setValue( "" );
307 ms.setParameters( new Parameter[]
308 {
309 included,
310 excluded,
311 exModules
312 } );
313 and.add( or );
314 and.add( ms );
315 fs.addAnd( and );
316 jnlpTask.execute();
317
318
319 String extSnippet = generateExtensions( fs, antProject, "" );
320
321 if ( masterJnlpFileName == null )
322 {
323 masterJnlpFileName = brandingToken;
324 }
325
326 Properties props = new Properties();
327 props.setProperty( "jnlp.codebase", localCodebase );
328 props.setProperty( "app.name", brandingToken );
329 props.setProperty( "app.title", project.getName() );
330 if ( project.getOrganization() != null )
331 {
332 props.setProperty( "app.vendor", project.getOrganization().getName() );
333 }
334 else
335 {
336 props.setProperty( "app.vendor", "Nobody" );
337 }
338 String description = project.getDescription() != null ? project.getDescription() : "No Project Description";
339 props.setProperty( "app.description", description );
340 props.setProperty( "branding.token", brandingToken );
341 props.setProperty( "master.jnlp.file.name", masterJnlpFileName );
342 props.setProperty( "netbeans.jnlp.fixPolicy", "false" );
343
344 StringBuilder stBuilder = new StringBuilder();
345 if ( additionalArguments != null )
346 {
347 StringTokenizer st = new StringTokenizer( additionalArguments );
348 while ( st.hasMoreTokens() )
349 {
350 String arg = st.nextToken();
351 if ( arg.startsWith( "-J" ) )
352 {
353 if ( stBuilder.length() > 0 )
354 {
355 stBuilder.append( ' ' );
356 }
357 stBuilder.append( arg.substring( 2 ) );
358 }
359 }
360 }
361 props.setProperty( "netbeans.run.params", stBuilder.toString() );
362
363 File masterJnlp = new File(
364 webstartBuildDir.getAbsolutePath() + File.separator + masterJnlpFileName + ".jnlp" );
365 filterCopy( masterJnlpFile, "master.jnlp", masterJnlp, props );
366
367
368 File startup = copyLauncher( outputDirectory, nbmBuildDirFile );
369 File jnlpDestination = new File(
370 webstartBuildDir.getAbsolutePath() + File.separator + "startup.jar" );
371
372 SignJar signTask = (SignJar) antProject.createTask( "signjar" );
373 signTask.setKeystore( keystore );
374 signTask.setStorepass( keystorepassword );
375 signTask.setAlias( keystorealias );
376 if ( keystoretype != null )
377 {
378 signTask.setStoretype( keystoretype );
379 }
380 signTask.setSignedjar( jnlpDestination );
381 signTask.setJar( startup );
382 signTask.execute();
383
384
385 DirectoryScanner ds = new DirectoryScanner();
386 ds.setBasedir( nbmBuildDirFile );
387 ds.setIncludes( new String[]
388 {
389 "**/locale/*.jar"
390 } );
391 ds.scan();
392 String[] includes = ds.getIncludedFiles();
393 StringBuilder brandRefs = new StringBuilder();
394 if ( includes != null && includes.length > 0 )
395 {
396 File brandingDir = new File( webstartBuildDir, "branding" );
397 brandingDir.mkdirs();
398 for ( String incBran : includes )
399 {
400 File source = new File( nbmBuildDirFile, incBran );
401 File dest = new File( brandingDir, source.getName() );
402 FileUtils.copyFile( source, dest );
403 brandRefs.append( " <jar href=\'branding/" ).append( dest.getName() ).append( "\'/>\n" );
404 }
405
406 signTask = (SignJar) antProject.createTask( "signjar" );
407 signTask.setKeystore( keystore );
408 signTask.setStorepass( keystorepassword );
409 signTask.setAlias( keystorealias );
410 if ( keystoretype != null )
411 {
412 signTask.setStoretype( keystoretype );
413 }
414
415 FileSet set = new FileSet();
416 set.setDir( brandingDir );
417 set.setIncludes( "*.jar" );
418 signTask.addFileset( set );
419 signTask.execute();
420 }
421
422 File modulesJnlp = new File(
423 webstartBuildDir.getAbsolutePath() + File.separator + "modules.jnlp" );
424 props.setProperty( "jnlp.branding.jars", brandRefs.toString() );
425 props.setProperty( "jnlp.resources", extSnippet );
426 filterCopy( null,
427
428 getLog().info( "Verifying generated webstartable content." );
429 VerifyJNLP verifyTask = (VerifyJNLP) antProject.createTask( "verifyjnlp" );
430 FileSet verify = new FileSet();
431 verify.setFile( masterJnlp );
432 verifyTask.addConfiguredFileset( verify );
433 verifyTask.execute();
434
435
436
437 if ( destinationFile.exists() )
438 {
439 destinationFile.delete();
440 }
441 ZipArchiver archiver = new ZipArchiver();
442 if ( codebase != null )
443 {
444 getLog().warn( "Defining <codebase>/${nbm.webstart.codebase} is generally unnecessary" );
445 archiver.addDirectory( webstartBuildDir );
446 }
447 else
448 {
449 archiver.addDirectory( webstartBuildDir, null, new String[] { "**/*.jnlp" } );
450 for ( final File jnlp : webstartBuildDir.listFiles() )
451 {
452 if ( !jnlp.getName().endsWith( ".jnlp" ) )
453 {
454 continue;
455 }
456 archiver.addResource( new PlexusIoResource() {
457 public @Override InputStream getContents() throws IOException
458 {
459 return new ByteArrayInputStream( FileUtils.fileRead( jnlp, "UTF-8" ).replace( localCodebase, "$$codebase" ).getBytes( "UTF-8" ) );
460 }
461 public @Override long getLastModified()
462 {
463 return jnlp.lastModified();
464 }
465 public @Override boolean isExisting()
466 {
467 return true;
468 }
469 public @Override long getSize()
470 {
471 return UNKNOWN_RESOURCE_SIZE;
472 }
473 public @Override URL getURL() throws IOException
474 {
475 return null;
476 }
477 public @Override String getName()
478 {
479 return jnlp.getAbsolutePath();
480 }
481 public @Override boolean isFile()
482 {
483 return true;
484 }
485 public @Override boolean isDirectory()
486 {
487 return false;
488 }
489 }, jnlp.getName(), archiver.getDefaultFileMode() );
490 }
491 }
492 File jdkhome = new File( System.getProperty( "java.home" ) );
493 File servlet = new File( jdkhome, "sample/jnlp/servlet/jnlp-servlet.jar" );
494 if ( ! servlet.isFile() )
495 {
496 servlet = new File( jdkhome.getParentFile(), "sample/jnlp/servlet/jnlp-servlet.jar" );
497 }
498 if ( servlet.isFile() )
499 {
500 archiver.addFile( servlet, "WEB-INF/lib/jnlp-servlet.jar" );
501 archiver.addResource( new PlexusIoResource() {
502 public @Override InputStream getContents() throws IOException
503 {
504 return new ByteArrayInputStream( ( "" +
505 "<web-app>\n" +
506 " <servlet>\n" +
507 " <servlet-name>JnlpDownloadServlet</servlet-name>\n" +
508 " <servlet-class>jnlp.sample.servlet.JnlpDownloadServlet</servlet-class>\n" +
509 " </servlet>\n" +
510 " <servlet-mapping>\n" +
511 " <servlet-name>JnlpDownloadServlet</servlet-name>\n" +
512 " <url-pattern>*.jnlp</url-pattern>\n" +
513 " </servlet-mapping>\n" +
514 "</web-app>\n" ).getBytes() );
515 }
516 public @Override long getLastModified()
517 {
518 return UNKNOWN_MODIFICATION_DATE;
519 }
520 public @Override boolean isExisting()
521 {
522 return true;
523 }
524 public @Override long getSize()
525 {
526 return UNKNOWN_RESOURCE_SIZE;
527 }
528 public @Override URL getURL() throws IOException
529 {
530 return null;
531 }
532 public @Override String getName()
533 {
534 return "web.xml";
535 }
536 public @Override boolean isFile()
537 {
538 return true;
539 }
540 public @Override boolean isDirectory()
541 {
542 return false;
543 }
544 }, "WEB-INF/web.xml", archiver.getDefaultFileMode() );
545 }
546 archiver.setDestFile( destinationFile );
547 archiver.createArchive();
548
549
550 projectHelper.attachArtifact( project, "war", webstartClassifier, destinationFile );
551
552 }
553 catch ( Exception ex )
554 {
555 throw new MojoExecutionException( "Error creating webstartable binary.", ex );
556 }
557 }
558
559
560
561
562
563 private File copyLauncher( File standaloneBuildDir, File builtInstallation )
564 throws IOException
565 {
566 File jnlpStarter =
567 new File( builtInstallation.getAbsolutePath() + File.separator + "harness" + File.separator + "jnlp"
568 + File.separator + "jnlp-launcher.jar" );
569
570 InputStream source = null;
571 FileOutputStream outstream = null;
572 try
573 {
574 if ( !jnlpStarter.exists() )
575 {
576 source = getClass().getClassLoader().getResourceAsStream(
577 "harness/jnlp/jnlp-launcher.jar" );
578 }
579 else
580 {
581 source = new FileInputStream( jnlpStarter );
582 }
583 File jnlpDestination = new File(
584 standaloneBuildDir.getAbsolutePath() + File.separator + "jnlp-launcher.jar" );
585
586 outstream = new FileOutputStream( jnlpDestination );
587 IOUtil.copy( source, outstream );
588 return jnlpDestination;
589 }
590 finally
591 {
592 IOUtil.close( source );
593 IOUtil.close( outstream );
594 }
595 }
596
597 private void filterCopy( File sourceFile, String resourcePath, File destinationFile, Properties filterProperties )
598 throws IOException
599 {
600
601 Reader source = null;
602 Writer destination = null;
603 try
604 {
605 InputStream instream;
606 if ( sourceFile != null )
607 {
608 instream = new FileInputStream( sourceFile );
609 }
610 else
611 {
612 instream = getClass().getClassLoader().getResourceAsStream( resourcePath );
613 }
614 FileOutputStream outstream = new FileOutputStream( destinationFile );
615
616 source = new BufferedReader( new InputStreamReader( instream, "UTF-8" ) );
617 destination = new OutputStreamWriter( outstream, "UTF-8" );
618
619
620 Reader reader = new InterpolationFilterReader( source, filterProperties, "${", "}" );
621
622 IOUtil.copy( reader, destination );
623 }
624 finally
625 {
626 IOUtil.close( source );
627 IOUtil.close( destination );
628 }
629 }
630
631
632
633
634
635
636
637
638
639 private String generateExtensions( FileSet files, Project antProject, String masterPrefix )
640 throws IOException
641 {
642 StringBuilder buff = new StringBuilder();
643 for ( String nm : files.getDirectoryScanner( antProject ).getIncludedFiles() )
644 {
645 File jar = new File( files.getDir( antProject ), nm );
646
647 if ( !jar.canRead() )
648 {
649 throw new IOException( "Cannot read file: " + jar );
650 }
651
652 JarFile theJar = new JarFile( jar );
653 String codenamebase = theJar.getManifest().getMainAttributes().getValue( "OpenIDE-Module" );
654 if ( codenamebase == null )
655 {
656 throw new IOException( "Not a NetBeans Module: " + jar );
657 }
658 {
659 int slash = codenamebase.indexOf( '/' );
660 if ( slash >= 0 )
661 {
662 codenamebase = codenamebase.substring( 0, slash );
663 }
664 }
665 String dashcnb = codenamebase.replace( '.', '-' );
666
667 buff.append( " <extension name='" ).append( codenamebase ).append( "' href='" ).append( masterPrefix ).append( dashcnb ).append( ".jnlp' />\n" );
668 theJar.close();
669 }
670 return buff.toString();
671
672 }
673 }