blob: 1d5647722723b7311a3c4f3c3c1672e3591607f8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.netbeans.nbm;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.GenerateKey;
import org.apache.tools.ant.taskdefs.SignJar;
import org.apache.tools.ant.taskdefs.Taskdef;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Parameter;
import org.apache.tools.ant.types.selectors.AndSelector;
import org.apache.tools.ant.types.selectors.FilenameSelector;
import org.apache.tools.ant.types.selectors.OrSelector;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.InterpolationFilterReader;
import org.netbeans.nbbuild.MakeJNLP;
import org.netbeans.nbbuild.ModuleSelector;
import org.netbeans.nbbuild.VerifyJNLP;
/**
* Create webstartable binaries for a 'nbm-application'.
* @author Johan Andrén
* @author Milos Kleint
* @since 3.0
*/
@Mojo(name="webstart-app", defaultPhase= LifecyclePhase.PACKAGE )
public class CreateWebstartAppMojo
extends AbstractNbmMojo
{
/**
* The Maven project.
*/
@org.apache.maven.plugins.annotations.Parameter(required=true, readonly=true, property="project")
private MavenProject project;
@Component
protected MavenProjectHelper projectHelper;
/**
* The branding token for the application based on NetBeans platform.
*/
@org.apache.maven.plugins.annotations.Parameter(required=true, property="netbeans.branding.token")
protected String brandingToken;
/**
* output directory where the the NetBeans application will be created.
*/
@org.apache.maven.plugins.annotations.Parameter(required=true, defaultValue="${project.build.directory}")
private File outputDirectory;
/**
* Ready-to-deploy WAR containing application in JNLP packaging.
*
*/
@org.apache.maven.plugins.annotations.Parameter(required=true, defaultValue="${project.build.directory}/${project.artifactId}-${project.version}-jnlp.war")
private File destinationFile;
/**
* Artifact Classifier to use for the webstart distributable zip file.
* @since 3.1
*/
@org.apache.maven.plugins.annotations.Parameter(defaultValue="webstart", property="nbm.webstart.classifier")
private String webstartClassifier;
/**
* Codebase value within *.jnlp files.
* <strong>Defining this parameter is generally a bad idea.</strong>
*/
@org.apache.maven.plugins.annotations.Parameter(property="nbm.webstart.codebase")
private String codebase;
/**
* A custom master JNLP file. If not defined, the
* <a href="http://mojo.codehaus.org/nbm-maven-plugin/masterjnlp.txt">default one</a> is used.
* The following expressions can be used within the file and will
* be replaced when generating content.
* <ul>
* <li>${jnlp.resources}</li>
* <li>${jnlp.codebase} - the 'codebase' parameter value is passed in.</li>
* <li>${app.name}</li>
* <li>${app.title}</li>
* <li>${app.vendor}</li>
* <li>${app.description}</li>
* <li>${branding.token} - the 'brandingToken' parameter value is passed in.</li>
* <li>${netbeans.jnlp.fixPolicy}</li>
* </ul>
*/
@org.apache.maven.plugins.annotations.Parameter
private File masterJnlpFile;
/**
* The basename (minus .jnlp extension) of the master JNLP file in the output.
* This file will be the entry point for javaws.
* Defaults to the branding token.
* @since 3.5
*/
@org.apache.maven.plugins.annotations.Parameter(property="master.jnlp.file.name")
private String masterJnlpFileName;
/**
* keystore location for signing the nbm file
*/
@org.apache.maven.plugins.annotations.Parameter(property="keystore")
private String keystore;
/**
* keystore password
*/
@org.apache.maven.plugins.annotations.Parameter(property="keystorepass")
private String keystorepassword;
/**
* keystore alias
*/
@org.apache.maven.plugins.annotations.Parameter(property="keystorealias")
private String keystorealias;
/**
* keystore type
* @since 3.5
*/
@org.apache.maven.plugins.annotations.Parameter(property="keystoretype")
private String keystoretype;
/**
* If set true, build-jnlp target creates versioning info in jnlp descriptors and version.xml files.
* This allows for incremental updates of Webstart applications, but requires download via
* JnlpDownloadServlet
* Defaults to false, which means versioning
* info is not generated (see
* http://java.sun.com/j2se/1.5.0/docs/guide/javaws/developersguide/downloadservletguide.html#resources).
*
*/
@org.apache.maven.plugins.annotations.Parameter(defaultValue="false", property="nbm.webstart.versions")
private boolean processJarVersions;
/**
* additional command line arguments. Eg.
* -J-Xdebug -J-Xnoagent -J-Xrunjdwp:transport=dt_socket,suspend=n,server=n,address=8888
* can be used to debug the IDE.
*/
@org.apache.maven.plugins.annotations.Parameter(property="netbeans.run.params")
private String additionalArguments;
/**
*
* @throws MojoExecutionException if an unexpected problem occurs
* @throws MojoFailureException if an expected problem occurs
*/
@Override
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( !"nbm-application".equals( project.getPackaging() ) )
{
throw new MojoExecutionException(
"This goal only makes sense on project with nbm-application packaging." );
}
Project antProject = antProject();
getLog().warn( "WARNING: Unsigned and self-signed WebStart applications are deprecated from JDK7u21 onwards. To ensure future correct functionality please use trusted certificate.");
if ( keystore != null && keystorealias != null && keystorepassword != null )
{
File ks = new File( keystore );
if ( !ks.exists() )
{
throw new MojoFailureException( "Cannot find keystore file at " + ks.getAbsolutePath() );
}
else
{
//proceed..
}
}
else if ( keystore != null || keystorepassword != null || keystorealias != null )
{
throw new MojoFailureException(
"If you want to sign the jnlp application, you need to define all three keystore related parameters." );
}
else
{
File generatedKeystore = new File( outputDirectory, "generated.keystore" );
if ( ! generatedKeystore.exists() )
{
getLog().warn( "Keystore related parameters not set, generating a default keystore." );
GenerateKey genTask = (GenerateKey) antProject.createTask( "genkey" );
genTask.setAlias( "jnlp" );
genTask.setStorepass( "netbeans" );
genTask.setDname( "CN=" + System.getProperty( "user.name" ) );
genTask.setKeystore( generatedKeystore.getAbsolutePath() );
genTask.execute();
}
keystore = generatedKeystore.getAbsolutePath();
keystorepassword = "netbeans";
keystorealias = "jnlp";
}
Taskdef taskdef = (Taskdef) antProject.createTask( "taskdef" );
taskdef.setClassname( "org.netbeans.nbbuild.MakeJNLP" );
taskdef.setName( "makejnlp" );
taskdef.execute();
taskdef = (Taskdef) antProject.createTask( "taskdef" );
taskdef.setClassname( "org.netbeans.nbbuild.VerifyJNLP" );
taskdef.setName( "verifyjnlp" );
taskdef.execute();
try
{
File webstartBuildDir = new File(
outputDirectory + File.separator + "webstart" + File.separator + brandingToken );
if ( webstartBuildDir.exists() )
{
FileUtils.deleteDirectory( webstartBuildDir );
}
webstartBuildDir.mkdirs();
final String localCodebase = codebase != null ? codebase : webstartBuildDir.toURI().toString();
getLog().info( "Generating webstartable binaries at " + webstartBuildDir.getAbsolutePath() );
File nbmBuildDirFile = new File( outputDirectory, brandingToken );
// FileUtils.copyDirectoryStructureIfModified( nbmBuildDirFile, webstartBuildDir );
MakeJNLP jnlpTask = (MakeJNLP) antProject.createTask( "makejnlp" );
jnlpTask.setDir( webstartBuildDir );
jnlpTask.setCodebase( localCodebase );
//TODO, how to figure verify excludes..
jnlpTask.setVerify( false );
jnlpTask.setPermissions( "<security><all-permissions/></security>" );
jnlpTask.setSignJars( true );
jnlpTask.setAlias( keystorealias );
jnlpTask.setKeystore( keystore );
jnlpTask.setStorePass( keystorepassword );
if ( keystoretype != null )
{
jnlpTask.setStoreType( keystoretype );
}
jnlpTask.setProcessJarVersions( processJarVersions );
FileSet fs = jnlpTask.createModules();
fs.setDir( nbmBuildDirFile );
OrSelector or = new OrSelector();
AndSelector and = new AndSelector();
FilenameSelector inc = new FilenameSelector();
inc.setName( "*/modules/**/*.jar" );
or.addFilename( inc );
inc = new FilenameSelector();
inc.setName( "*/lib/**/*.jar" );
or.addFilename( inc );
inc = new FilenameSelector();
inc.setName( "*/core/**/*.jar" );
or.addFilename( inc );
ModuleSelector ms = new ModuleSelector();
Parameter included = new Parameter();
included.setName( "includeClusters" );
included.setValue( "" );
Parameter excluded = new Parameter();
excluded.setName( "excludeClusters" );
excluded.setValue( "" );
Parameter exModules = new Parameter();
exModules.setName( "excludeModules" );
exModules.setValue( "" );
ms.setParameters( new Parameter[]
{
included,
excluded,
exModules
} );
and.add( or );
and.add( ms );
fs.addAnd( and );
jnlpTask.execute();
//TODO is it really netbeans/
String extSnippet = generateExtensions( fs, antProject, "" ); // "netbeans/"
if ( masterJnlpFileName == null )
{
masterJnlpFileName = brandingToken;
}
Properties props = new Properties();
props.setProperty( "jnlp.codebase", localCodebase );
props.setProperty( "app.name", brandingToken );
props.setProperty( "app.title", project.getName() );
if ( project.getOrganization() != null )
{
props.setProperty( "app.vendor", project.getOrganization().getName() );
}
else
{
props.setProperty( "app.vendor", "Nobody" );
}
String description = project.getDescription() != null ? project.getDescription() : "No Project Description";
props.setProperty( "app.description", description );
props.setProperty( "branding.token", brandingToken );
props.setProperty( "master.jnlp.file.name", masterJnlpFileName );
props.setProperty( "netbeans.jnlp.fixPolicy", "false" );
StringBuilder stBuilder = new StringBuilder();
if ( additionalArguments != null )
{
StringTokenizer st = new StringTokenizer( additionalArguments );
while ( st.hasMoreTokens() )
{
String arg = st.nextToken();
if ( arg.startsWith( "-J" ) )
{
if ( stBuilder.length() > 0 )
{
stBuilder.append( ' ' );
}
stBuilder.append( arg.substring( 2 ) );
}
}
}
props.setProperty( "netbeans.run.params", stBuilder.toString() );
File masterJnlp = new File(
webstartBuildDir.getAbsolutePath() + File.separator + masterJnlpFileName + ".jnlp" );
filterCopy( masterJnlpFile, "master.jnlp", masterJnlp, props );
File startup = copyLauncher( outputDirectory, nbmBuildDirFile );
File jnlpDestination = new File(
webstartBuildDir.getAbsolutePath() + File.separator + "startup.jar" );
SignJar signTask = (SignJar) antProject.createTask( "signjar" );
signTask.setKeystore( keystore );
signTask.setStorepass( keystorepassword );
signTask.setAlias( keystorealias );
if ( keystoretype != null )
{
signTask.setStoretype( keystoretype );
}
signTask.setSignedjar( jnlpDestination );
signTask.setJar( startup );
signTask.execute();
//branding
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir( nbmBuildDirFile );
ds.setIncludes( new String[]
{
"**/locale/*.jar"
} );
ds.scan();
String[] includes = ds.getIncludedFiles();
StringBuilder brandRefs = new StringBuilder();
if ( includes != null && includes.length > 0 )
{
File brandingDir = new File( webstartBuildDir, "branding" );
brandingDir.mkdirs();
for ( String incBran : includes )
{
File source = new File( nbmBuildDirFile, incBran );
File dest = new File( brandingDir, source.getName() );
FileUtils.copyFile( source, dest );
brandRefs.append( " <jar href=\'branding/" ).append( dest.getName() ).append( "\'/>\n" );
}
signTask = (SignJar) antProject.createTask( "signjar" );
signTask.setKeystore( keystore );
signTask.setStorepass( keystorepassword );
signTask.setAlias( keystorealias );
if ( keystoretype != null )
{
signTask.setStoretype( keystoretype );
}
FileSet set = new FileSet();
set.setDir( brandingDir );
set.setIncludes( "*.jar" );
signTask.addFileset( set );
signTask.execute();
}
File modulesJnlp = new File(
webstartBuildDir.getAbsolutePath() + File.separator + "modules.jnlp" );
props.setProperty( "jnlp.branding.jars", brandRefs.toString() );
props.setProperty( "jnlp.resources", extSnippet );
filterCopy( null, /* filename is historical */"branding.jnlp", modulesJnlp, props );
getLog().info( "Verifying generated webstartable content." );
VerifyJNLP verifyTask = (VerifyJNLP) antProject.createTask( "verifyjnlp" );
FileSet verify = new FileSet();
verify.setFile( masterJnlp );
verifyTask.addConfiguredFileset( verify );
verifyTask.execute();
// create zip archive
if ( destinationFile.exists() )
{
destinationFile.delete();
}
ZipArchiver archiver = new ZipArchiver();
if ( codebase != null )
{
getLog().warn( "Defining <codebase>/${nbm.webstart.codebase} is generally unnecessary" );
archiver.addDirectory( webstartBuildDir );
}
else
{
archiver.addDirectory( webstartBuildDir, null, new String[] { "**/*.jnlp" } );
for ( final File jnlp : webstartBuildDir.listFiles() )
{
if ( !jnlp.getName().endsWith( ".jnlp" ) )
{
continue;
}
archiver.addResource( new PlexusIoResource() {
public @Override InputStream getContents() throws IOException
{
return new ByteArrayInputStream( FileUtils.fileRead( jnlp, "UTF-8" ).replace( localCodebase, "$$codebase" ).getBytes( "UTF-8" ) );
}
public @Override long getLastModified()
{
return jnlp.lastModified();
}
public @Override boolean isExisting()
{
return true;
}
public @Override long getSize()
{
return UNKNOWN_RESOURCE_SIZE;
}
public @Override URL getURL() throws IOException
{
return null;
}
public @Override String getName()
{
return jnlp.getAbsolutePath();
}
public @Override boolean isFile()
{
return true;
}
public @Override boolean isDirectory()
{
return false;
}
@Override
public boolean isSymbolicLink()
{
return false;
}
}, jnlp.getName(), archiver.getDefaultFileMode() );
}
}
File jdkhome = new File( System.getProperty( "java.home" ) );
File servlet = new File( jdkhome, "sample/jnlp/servlet/jnlp-servlet.jar" );
if ( ! servlet.isFile() )
{
servlet = new File( jdkhome.getParentFile(), "sample/jnlp/servlet/jnlp-servlet.jar" );
}
if ( servlet.isFile() )
{
archiver.addFile( servlet, "WEB-INF/lib/jnlp-servlet.jar" );
archiver.addResource( new PlexusIoResource() {
public @Override InputStream getContents() throws IOException
{
return new ByteArrayInputStream( ( "" +
"<web-app>\n" +
" <servlet>\n" +
" <servlet-name>JnlpDownloadServlet</servlet-name>\n" +
" <servlet-class>jnlp.sample.servlet.JnlpDownloadServlet</servlet-class>\n" +
" </servlet>\n" +
" <servlet-mapping>\n" +
" <servlet-name>JnlpDownloadServlet</servlet-name>\n" +
" <url-pattern>*.jnlp</url-pattern>\n" +
" </servlet-mapping>\n" +
"</web-app>\n" ).getBytes() );
}
public @Override long getLastModified()
{
return UNKNOWN_MODIFICATION_DATE;
}
public @Override boolean isExisting()
{
return true;
}
public @Override long getSize()
{
return UNKNOWN_RESOURCE_SIZE;
}
public @Override URL getURL() throws IOException
{
return null;
}
public @Override String getName()
{
return "web.xml";
}
public @Override boolean isFile()
{
return true;
}
public @Override boolean isDirectory()
{
return false;
}
@Override
public boolean isSymbolicLink()
{
return false;
}
}, "WEB-INF/web.xml", archiver.getDefaultFileMode() );
}
archiver.setDestFile( destinationFile );
archiver.createArchive();
// attach standalone so that it gets installed/deployed
projectHelper.attachArtifact( project, "war", webstartClassifier, destinationFile );
}
catch ( Exception ex )
{
throw new MojoExecutionException( "Error creating webstartable binary.", ex );
}
}
/**
* @param standaloneBuildDir
* @return The name of the jnlp-launcher jarfile in the build directory
*/
private File copyLauncher( File standaloneBuildDir, File builtInstallation )
throws IOException
{
File jnlpStarter =
new File( builtInstallation.getAbsolutePath() + File.separator + "harness" + File.separator + "jnlp"
+ File.separator + "jnlp-launcher.jar" );
// buffer so it isn't reading a byte at a time!
InputStream source = null;
FileOutputStream outstream = null;
try
{
if ( !jnlpStarter.exists() )
{
source = getClass().getClassLoader().getResourceAsStream(
"harness/jnlp/jnlp-launcher.jar" );
}
else
{
source = new FileInputStream( jnlpStarter );
}
File jnlpDestination = new File(
standaloneBuildDir.getAbsolutePath() + File.separator + "jnlp-launcher.jar" );
outstream = new FileOutputStream( jnlpDestination );
IOUtil.copy( source, outstream );
return jnlpDestination;
}
finally
{
IOUtil.close( source );
IOUtil.close( outstream );
}
}
private void filterCopy( File sourceFile, String resourcePath, File destinationFile, Properties filterProperties )
throws IOException
{
// buffer so it isn't reading a byte at a time!
Reader source = null;
Writer destination = null;
try
{
InputStream instream;
if ( sourceFile != null )
{
instream = new FileInputStream( sourceFile );
}
else
{
instream = getClass().getClassLoader().getResourceAsStream( resourcePath );
}
FileOutputStream outstream = new FileOutputStream( destinationFile );
source = new BufferedReader( new InputStreamReader( instream, "UTF-8" ) );
destination = new OutputStreamWriter( outstream, "UTF-8" );
// support ${token}
Reader reader = new InterpolationFilterReader( source, filterProperties, "${", "}" );
IOUtil.copy( reader, destination );
}
finally
{
IOUtil.close( source );
IOUtil.close( destination );
}
}
/**
* copied from MakeMasterJNLP ant task.
* @param files
* @param antProject
* @param masterPrefix
* @return
* @throws java.io.IOException
*/
private String generateExtensions( FileSet files, Project antProject, String masterPrefix )
throws IOException
{
StringBuilder buff = new StringBuilder();
for ( String nm : files.getDirectoryScanner( antProject ).getIncludedFiles() )
{
File jar = new File( files.getDir( antProject ), nm );
if ( !jar.canRead() )
{
throw new IOException( "Cannot read file: " + jar );
}
JarFile theJar = new JarFile( jar );
Attributes attr = theJar.getManifest().getMainAttributes();
String codenamebase = attr.getValue( "OpenIDE-Module" );
if ( codenamebase == null )
{
codenamebase = attr.getValue("Bundle-SymbolicName");
}
if ( codenamebase == null )
{
throw new IOException( "Not a NetBeans Module: " + jar );
}
// see http://hg.netbeans.org/main-silver/rev/87823abb86d9
if (codenamebase.equals("org.objectweb.asm.all")
&& jar.getParentFile().getName().equals("core")
&& jar.getParentFile().getParentFile().getName().startsWith("platform")) {
continue;
}
{
int slash = codenamebase.indexOf( '/' );
if ( slash >= 0 )
{
codenamebase = codenamebase.substring( 0, slash );
}
}
String dashcnb = codenamebase.replace( '.', '-' );
buff.append( " <extension name='" ).append( codenamebase ).append( "' href='" ).append( masterPrefix ).append( dashcnb ).append( ".jnlp' />\n" );
theJar.close();
}
return buff.toString();
}
}