PR: MNG-1130

Submitted by: Jerome Lacoste
Reviewed by:  Brett Porter
Add a jar signing mojo
Modifications made: formatting, fix tests to run on Windows


git-svn-id: https://svn.apache.org/repos/asf/maven/components/trunk@327889 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Brett Leslie Porter 2005-10-24 00:29:52 +00:00
parent d7c3795f1c
commit b27079e743
9 changed files with 770 additions and 16 deletions

View File

@ -9,6 +9,19 @@
<packaging>maven-plugin</packaging> <packaging>maven-plugin</packaging>
<name>Maven Jar Plugin</name> <name>Maven Jar Plugin</name>
<version>2.1-SNAPSHOT</version> <version>2.1-SNAPSHOT</version>
<contributors>
<contributor>
<name>Jerome Lacoste</name>
<email>jerome@coffeebreaks.org</email>
<organization>CoffeeBreaks</organization>
<organizationUrl>http://www.coffeebreaks.org</organizationUrl>
<timezone>+1</timezone>
<roles>
<role>Java Developer</role>
</roles>
</contributor>
</contributors>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
@ -19,5 +32,10 @@
<artifactId>maven-archiver</artifactId> <artifactId>maven-archiver</artifactId>
<version>2.0</version> <version>2.0</version>
</dependency> </dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -46,9 +46,8 @@ public abstract class AbstractJarMojo
* @parameter expression="${project.build.directory}" * @parameter expression="${project.build.directory}"
* @required * @required
* @readonly * @readonly
* @todo Change type to File
*/ */
private String basedir; private File basedir;
/** /**
* Name of the generated JAR. * Name of the generated JAR.
@ -97,21 +96,18 @@ public abstract class AbstractJarMojo
return project; return project;
} }
protected final File getBaseDir()
{
return basedir;
}
/** /**
* Overload this to produce a test-jar, for example. * Overload this to produce a test-jar, for example.
*/ */
protected abstract String getClassifier(); protected abstract String getClassifier();
/** protected static File getJarFile( File basedir, String finalName, String classifier )
* Generates the JAR.
*
* @todo Add license files in META-INF directory.
*/
public File createArchive()
throws MojoExecutionException
{ {
String classifier = getClassifier();
if ( classifier == null ) if ( classifier == null )
{ {
classifier = ""; classifier = "";
@ -121,7 +117,18 @@ public abstract class AbstractJarMojo
classifier = "-" + classifier; classifier = "-" + classifier;
} }
File jarFile = new File( basedir, finalName + classifier + ".jar" ); return new File( basedir, finalName + classifier + ".jar" );
}
/**
* Generates the JAR.
*
* @todo Add license files in META-INF directory.
*/
public File createArchive()
throws MojoExecutionException
{
File jarFile = getJarFile( basedir, finalName, getClassifier() );
MavenArchiver archiver = new MavenArchiver(); MavenArchiver archiver = new MavenArchiver();

View File

@ -0,0 +1,425 @@
package org.apache.maven.plugin.jar;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed 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.
*/
import org.apache.commons.lang.SystemUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
/**
* Signs a JAR using jarsigner.
*
* @author <a href="jerome@coffeebreaks.org">Jerome Lacoste</a>
* @version $Id$
* @goal sign
* @phase package
* @requiresProject
* @todo refactor the common code with javadoc plugin
*/
public class JarSignMojo
extends AbstractMojo
{
/**
* @parameter expression="${workingdir}" default-value="${basedir}"
* @required
*/
private File workingDirectory;
/**
* Directory containing the generated JAR.
*
* @parameter expression="${project.build.directory}"
* @required
* @readonly
*/
private File basedir;
/**
* Name of the generated JAR (without classifier and extension).
*
* @parameter alias="jarname" expression="${project.build.finalName}"
* @required
*/
private String finalName;
/**
* Path of the jar to sign. When specified, the finalName is ignored.
*
* @parameter alias="jarpath"
* @required
*/
private String jarPath;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${keystore}"
*/
private String keystore;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${storepass}"
*/
private String storepass;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${keypass}"
*/
private String keypass;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${sigfile}"
* @todo make a File?
*/
private String sigfile;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${signedjar}" default-value="${project.build.directory}/signed/${project.build.finalName}.jar"
* @required
* @todo make a File?
*/
private String signedjar;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${type}"
*/
private String type;
/**
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${alias}"
* @required
*/
private String alias;
/**
* Enable verbose
* See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
*
* @parameter expression="${verbose}" default-value="false"
*/
private boolean verbose;
public void execute()
throws MojoExecutionException
{
List arguments = new ArrayList();
Commandline commandLine = new Commandline();
commandLine.setExecutable( getJarsignerPath() );
addArgIf( arguments, verbose, "-verbose" );
// I believe Commandline to add quotes where appropriate, although I haven't tested it enough.
// FIXME addArgIfNotEmpty will break those parameters containing a space.
// Look at webapp:gen-keystore for a way to fix that
addArgIfNotEmpty( arguments, "-keystore", this.keystore );
addArgIfNotEmpty( arguments, "-storepass", this.storepass );
addArgIfNotEmpty( arguments, "-keypass", this.keypass );
addArgIfNotEmpty( arguments, "-signedjar", this.signedjar );
addArgIfNotEmpty( arguments, "-storetype", this.type );
addArgIfNotEmpty( arguments, "-sigfile", this.sigfile );
if ( jarPath != null )
{
arguments.add( new File( jarPath ) );
}
else
{
arguments.add( AbstractJarMojo.getJarFile( basedir, finalName, null ) );
}
addArgIf( arguments, alias != null, this.alias );
for ( Iterator it = arguments.iterator(); it.hasNext(); )
{
commandLine.createArgument().setValue( it.next().toString() );
}
commandLine.setWorkingDirectory( workingDirectory.getAbsolutePath() );
createParentDirIfNecessary( signedjar );
getLog().debug( "Executing: " + commandLine );
// jarsigner may ask for some input if the parameters are missing or incorrect.
// This should take care of it and make it fail gracefully
final InputStream inputStream = new InputStream()
{
public int read()
{
return -1;
}
};
StreamConsumer outConsumer = new StreamConsumer()
{
public void consumeLine( String line )
{
getLog().info( line );
}
};
final StringBuffer errBuffer = new StringBuffer();
StreamConsumer errConsumer = new StreamConsumer()
{
public void consumeLine( String line )
{
errBuffer.append( line );
getLog().warn( line );
}
};
try
{
int result = executeCommandLine( commandLine, inputStream, outConsumer, errConsumer );
if ( result != 0 )
{
throw new MojoExecutionException( "Result of " + commandLine + " execution is: \'" + result + "\': "
+ errBuffer.toString() + "." );
}
}
catch ( CommandLineException e )
{
throw new MojoExecutionException( "command execution failed", e );
}
}
private void createParentDirIfNecessary( final String file )
{
if ( file != null )
{
final File fileDir = new File( file ).getParentFile();
if ( fileDir != null )
{ // not a relative path
boolean mkdirs = fileDir.mkdirs();
getLog().debug( "mdkirs: " + mkdirs + " " + fileDir );
}
}
}
// taken from JavadocReport then slightly refactored
// should probably share with other plugins that use $JAVA_HOME/bin tools
/**
* Get the path of jarsigner tool depending the OS.
*
* @return the path of the jarsigner tool
*/
private String getJarsignerPath()
{
return getJDKCommandPath( "jarsigner", getLog() );
}
private static String getJDKCommandPath( String command, Log logger )
{
String path = getJDKCommandExe( command ).getAbsolutePath();
logger.debug( command + " executable=[" + path + "]" );
return path;
}
private static File getJDKCommandExe( String command )
{
String fullCommand = command + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" );
File exe;
// For IBM's JDK 1.2
if ( SystemUtils.IS_OS_AIX )
{
exe = new File( SystemUtils.getJavaHome() + "/../sh", fullCommand );
}
else if ( SystemUtils.IS_OS_MAC_OSX )
{
exe = new File( SystemUtils.getJavaHome() + "/bin", fullCommand );
}
else
{
exe = new File( SystemUtils.getJavaHome() + "/../bin", fullCommand );
}
return exe;
}
// Helper methods. Could/should be shared e.g. with JavadocReport
/**
* Convenience method to add an argument to the <code>command line</code>
* conditionally based on the given flag.
*
* @param arguments
* @param b the flag which controls if the argument is added or not.
* @param value the argument value to be added.
*/
private void addArgIf( List arguments, boolean b, String value )
{
if ( b )
{
arguments.add( value );
}
}
/**
* Convenience method to add an argument to the <code>command line</code>
* if the the value is not null or empty.
* <p>
* Moreover, the value could be comma separated.
*
* @param arguments
* @param key the argument name.
* @param value the argument value to be added.
* @see #addArgIfNotEmpty(java.util.List,String,String,boolean)
*/
private void addArgIfNotEmpty( List arguments, String key, String value )
{
addArgIfNotEmpty( arguments, key, value, false );
}
/**
* Convenience method to add an argument to the <code>command line</code>
* if the the value is not null or empty.
* <p>
* Moreover, the value could be comma separated.
*
* @param arguments
* @param key the argument name.
* @param value the argument value to be added.
* @param repeatKey repeat or not the key in the command line
*/
private void addArgIfNotEmpty( List arguments, String key, String value, boolean repeatKey )
{
if ( !StringUtils.isEmpty( value ) )
{
arguments.add( key );
StringTokenizer token = new StringTokenizer( value, "," );
while ( token.hasMoreTokens() )
{
String current = token.nextToken().trim();
if ( !StringUtils.isEmpty( current ) )
{
arguments.add( current );
if ( token.hasMoreTokens() && repeatKey )
{
arguments.add( key );
}
}
}
}
}
//
// methods used for tests purposes - allow mocking and simulate automatic setters
//
protected int executeCommandLine( Commandline commandLine, InputStream inputStream, StreamConsumer stream1,
StreamConsumer stream2 )
throws CommandLineException
{
return CommandLineUtils.executeCommandLine( commandLine, inputStream, stream1, stream2 );
}
public void setWorkingDir( File workingDir )
{
this.workingDirectory = workingDir;
}
public void setBasedir( File basedir )
{
this.basedir = basedir;
}
public void setKeystore( String keystore )
{
this.keystore = keystore;
}
public void setKeypass( String keypass )
{
this.keypass = keypass;
}
public void setSignedJar( String signedjar )
{
this.signedjar = signedjar;
}
public void setAlias( String alias )
{
this.alias = alias;
}
// hiding for now - I don't think this is required to be seen
/*
public void setFinalName( String finalName ) {
this.finalName = finalName;
}
*/
public void setJarPath( String jarPath )
{
this.jarPath = jarPath;
}
public void setStorepass( String storepass )
{
this.storepass = storepass;
}
public void setSigFile( String sigfile )
{
this.sigfile = sigfile;
}
public void setType( String type )
{
this.type = type;
}
public void setVerbose( boolean verbose )
{
this.verbose = verbose;
}
}

View File

@ -2,11 +2,11 @@
Maven 2 JAR Plugin Maven 2 JAR Plugin
------ ------
Maven 2 JAR Plugin How to use
Builds your project into a jar Brief examples on how to use the jar:jar and jar:sign goals.
*How to Use * How to use jar:jar
If the packaging of your project is set to 'jar', this plugin is executed If the packaging of your project is set to 'jar', this plugin is executed
whenever it passes the "package" phase. Have it executed whenever it passes the "package" phase. Have it executed
@ -18,3 +18,45 @@ Maven 2 JAR Plugin
From your project's target directory you'll able to see the generated jar file. From your project's target directory you'll able to see the generated jar file.
* How to configure jar:sign using pom.xml
If you need to sign a jar, when using the 'jar' packaging, you just need to configure
the sign goal appropriately for the signing to occur automatically during the package phase.
-------------------
<project>
...
<packaging>jar</packaging>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<keystore>/path/to/your/keystore</keystore>
<alias>youralias</alias>
<storepass>yourstorepassword</storepass>
<!--signedjar>${project.build.directory}/signed/${project.build.finalName}.jar</signedjar-->
</configuration>
</plugin>
</plugins>
</build>
</project>
-------------------
* How to use jar:sign specifying parameters on the command line
-------------------
m2 jar:sign -Dkeystore=/path/to/your/keystore -Dstorepass=yourstorepassword -Dalias=youralias
-------------------
For full documentation, click {{{index.html}here}}.

View File

@ -0,0 +1,13 @@
------
Maven 2 JAR Plugin
------
Maven 2 JAR Plugin
This plugin provides the capability to manipulate jars. Currently it can create jars for your project sources or tests classes. To achieve this use
"jar:jar" or "jar:test-jar".
It also provides the capability to sign a jar file with the goal "jar:sign".
To learn how to use the plugin, click {{{howto.html}here}}.

View File

@ -13,7 +13,8 @@
</links> </links>
<menu name="Maven JAR Quickstart"> <menu name="Maven JAR Quickstart">
<item name="Overview" href="howto.html"/> <item name="Introduction" href="introduction.html"/>
<item name="How to use" href="howto.html"/>
</menu> </menu>
${reports} ${reports}
</body> </body>

View File

@ -0,0 +1,210 @@
package org.apache.maven.plugin.jar;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed 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.
*/
import junit.framework.TestCase;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* These unit tests only check whether the generated command lines are correct.
* Really running the command would mean checking the results, which is too painful and not really a unit test.
* It would probably require to 'jarsigner -verify' the resulting signed jar and I believe it would make the code
* too complex with very few benefits.
*
* @author Jerome Lacoste <jerome@coffeebreaks.org>
* @version $Id:$
*/
public class JarSignMojoTest
extends TestCase
{
private MockJarSignMojo mojo;
static class MockJarSignMojo
extends JarSignMojo
{
public int executeResult;
public List commandLines = new ArrayList();
public String failureMsg;
public Map systemProperties = new HashMap();
protected int executeCommandLine( Commandline commandLine, InputStream inputStream, StreamConsumer stream1,
StreamConsumer stream2 )
throws CommandLineException
{
commandLines.add( commandLine );
if ( failureMsg != null )
{
throw new CommandLineException( failureMsg );
}
return executeResult;
}
protected String getSystemProperty( String key )
{
return (String) systemProperties.get( key );
}
}
public void setUp()
throws IOException
{
mojo = new MockJarSignMojo();
mojo.executeResult = 0;
// it doesn't really matter if the paths are not cross-platform, we don't execute the command lines anyway
File basedir = new File( System.getProperty( "java.io.tmpdir" ) );
mojo.setBasedir( basedir );
mojo.setWorkingDir( basedir );
mojo.setSignedJar( "/tmp/signed/file-version.jar" );
mojo.setAlias( "alias" );
mojo.setKeystore( "/tmp/keystore" );
mojo.setKeypass( "secretpassword" );
}
public void tearDown()
{
mojo = null;
}
public void testPleaseMaven()
{
assertTrue( true );
}
/**
*/
public void testRunOK()
throws MojoExecutionException
{
mojo.execute();
String[] expectedArguments = {
"-keystore",
"/tmp/keystore",
"-keypass",
"secretpassword",
"-signedjar",
"/tmp/signed/file-version.jar",
getNullJar(),
"alias" };
checkMojo( expectedArguments );
}
/**
*/
public void testRunFailure()
{
mojo.executeResult = 1;
// any missing argument should produce this. Let's simulate a missing alias
mojo.setAlias( null );
try
{
mojo.execute();
fail( "expected failure" );
}
catch ( MojoExecutionException e )
{
assertTrue( e.getMessage().startsWith( "Result of " ) );
}
String[] expectedArguments = {
"-keystore",
"/tmp/keystore",
"-keypass",
"secretpassword",
"-signedjar",
"/tmp/signed/file-version.jar",
getNullJar() };
checkMojo( expectedArguments );
}
private String getNullJar()
{
String value = System.getProperty( "java.io.tmpdir" );
if ( !value.endsWith( "\\" ) && !value.endsWith( "/" ) )
{
value += "/";
}
value += "null.jar";
return value;
}
/**
*/
public void testRunError()
{
mojo.failureMsg = "simulated failure";
try
{
mojo.execute();
fail( "expected failure" );
}
catch ( MojoExecutionException e )
{
assertEquals( "command execution failed", e.getMessage() );
}
String[] expectedArguments = {
"-keystore",
"/tmp/keystore",
"-keypass",
"secretpassword",
"-signedjar",
"/tmp/signed/file-version.jar",
getNullJar(),
"alias" };
checkMojo( expectedArguments );
}
private void checkMojo( String[] expectedCommandLineArguments )
{
assertEquals( 1, mojo.commandLines.size() );
Commandline commandline = (Commandline) mojo.commandLines.get( 0 );
String[] arguments = commandline.getArguments();
// isn't there an assertEquals for arrays?
/*
for (int i = 0; i < arguments.length; i++ ) {
System.out.println( arguments[ i ] );
}
*/
assertEquals( "Differing number of arguments", expectedCommandLineArguments.length, arguments.length );
for ( int i = 0; i < arguments.length; i++ )
{
assertEquals( expectedCommandLineArguments[i], arguments[i] );
}
}
}

View File

@ -0,0 +1,7 @@
A pom.xml that can be used to make some functional tests.
These values are of course specific to my environment.
Not sure what to do with this. Should I turn this into a fully automated functional test?
The keystore was generated using:
keytool -genkey -alias m2 -keypass m2m2m2 -keystore keystore -storepass m2m2m2 -dname "cn=www.example.com, ou=None, L=Seattle, ST=Washington, o=ExampleOrg, c=US"

View File

@ -0,0 +1,31 @@
<!-- Test project which creates and signs a jar artifact -->
<project>
<modelVersion>4.0.0</modelVersion>
<artifactId>jar-mng-1130-0</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Test Case for MNG-1130</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<keystore>keystore</keystore>
<alias>m2</alias>
<storepass>m2m2m2</storepass>
<!--signedjar>${project.build.directory}/signed/${project.build.finalName}.jar</signedjar-->
<!--type></type-->
</configuration>
</plugin>
</plugins>
</build>
</project>