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>
<name>Maven Jar Plugin</name>
<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>
<dependency>
<groupId>org.apache.maven</groupId>
@ -19,5 +32,10 @@
<artifactId>maven-archiver</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
</project>

View File

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

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
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
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.
* 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>
<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>
${reports}
</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>