All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.jarsigner.AbstractJarsignerMojo Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
package org.apache.maven.plugins.jarsigner;

/*
 * 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.
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.zip.ZipInputStream;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.Os;
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;

/**
 * Maven Jarsigner Plugin base class.
 *
 * @author Christian Schulte
 * @version $Id: AbstractJarsignerMojo.java 817065 2009-09-20 17:51:11Z bentmann $
 */
public abstract class AbstractJarsignerMojo
    extends AbstractMojo
{

    /**
     * See options.
     *
     * @parameter expression="${jarsigner.verbose}" default-value="false"
     */
    private boolean verbose;

    /**
     * The maximum memory available to the JAR signer, e.g. 256M. See -Xmx for more details.
     * 
     * @parameter expression="${jarsigner.maxMemory}"
     */
    private String maxMemory;

    /**
     * Archive to process. If set, neither the project artifact nor any attachments or archive sets are processed.
     *
     * @parameter expression="${jarsigner.archive}"
     */
    private File archive;

    /**
     * The base directory to scan for JAR files using Ant-like inclusion/exclusion patterns.
     * 
     * @parameter expression="${jarsigner.archiveDirectory}"
     * @since 1.1
     */
    private File archiveDirectory;

    /**
     * The Ant-like inclusion patterns used to select JAR files to process. The patterns must be relative to the
     * directory given by the parameter {@link #archiveDirectory}. By default, the pattern
     * **/*.?ar is used.
     * 
     * @parameter
     * @since 1.1
     */
    private String[] includes = { "**/*.?ar" };

    /**
     * The Ant-like exclusion patterns used to exclude JAR files from processing. The patterns must be relative to the
     * directory given by the parameter {@link #archiveDirectory}.
     * 
     * @parameter
     * @since 1.1
     */
    private String[] excludes = {};

    /**
     * List of additional arguments to append to the jarsigner command line.
     *
     * @parameter expression="${jarsigner.arguments}"
     */
    private String[] arguments;

    /**
     * Set to {@code true} to disable the plugin.
     *
     * @parameter expression="${jarsigner.skip}" default-value="false"
     */
    private boolean skip;

    /**
     * Controls processing of the main artifact produced by the project.
     * 
     * @parameter expression="${jarsigner.processMainArtifact}" default-value="true"
     * @since 1.1
     */
    private boolean processMainArtifact;

    /**
     * Controls processing of project attachments. If enabled, attached artifacts that are no JAR/ZIP files will be
     * automatically excluded from processing.
     * 
     * @parameter expression="${jarsigner.processAttachedArtifacts}" default-value="true"
     * @since 1.1
     */
    private boolean processAttachedArtifacts;

    /**
     * Controls processing of project attachments.
     * 
     * @parameter expression="${jarsigner.attachments}"
     * @deprecated As of version 1.1 in favor of the new parameter processAttachedArtifacts.
     */
    private Boolean attachments;

    /**
     * A set of artifact classifiers describing the project attachments that should be processed. This parameter is only
     * relevant if {@link #processAttachedArtifacts} is true. If empty, all attachments are included.
     * 
     * @parameter
     * @since 1.2
     */
    private String[] includeClassifiers;

    /**
     * A set of artifact classifiers describing the project attachments that should not be processed. This parameter is
     * only relevant if {@link #processAttachedArtifacts} is true. If empty, no attachments are excluded.
     * 
     * @parameter
     * @since 1.2
     */
    private String[] excludeClassifiers;

    /**
     * The Maven project.
     *
     * @parameter default-value="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * The path to the jarsigner we are going to use.
     */
    private String executable;

    public final void execute()
        throws MojoExecutionException
    {
        if ( !this.skip )
        {
            this.executable = getExecutable();

            int processed = 0;

            if ( this.archive != null )
            {
                processArchive( this.archive );
                processed++;
            }
            else
            {
                if ( processMainArtifact )
                {
                    processed += processArtifact( this.project.getArtifact() ) ? 1 : 0;
                }

                if ( processAttachedArtifacts && !Boolean.FALSE.equals( attachments ) )
                {
                    Collection includes = new HashSet();
                    if ( includeClassifiers != null )
                    {
                        includes.addAll( Arrays.asList( includeClassifiers ) );
                    }

                    Collection excludes = new HashSet();
                    if ( excludeClassifiers != null )
                    {
                        excludes.addAll( Arrays.asList( excludeClassifiers ) );
                    }

                    for ( Iterator it = this.project.getAttachedArtifacts().iterator(); it.hasNext(); )
                    {
                        final Artifact artifact = (Artifact) it.next();

                        if ( !includes.isEmpty() && !includes.contains( artifact.getClassifier() ) )
                        {
                            continue;
                        }

                        if ( excludes.contains( artifact.getClassifier() ) )
                        {
                            continue;
                        }

                        processed += processArtifact( artifact ) ? 1 : 0;
                    }
                }
                else
                {
                    if ( verbose )
                    {
                        getLog().info( getMessage( "ignoringAttachments" ) );
                    }
                    else
                    {
                        getLog().debug( getMessage( "ignoringAttachments" ) );
                    }
                }

                if ( archiveDirectory != null )
                {
                    String includeList = ( includes != null ) ? StringUtils.join( includes, "," ) : null;
                    String excludeList = ( excludes != null ) ? StringUtils.join( excludes, "," ) : null;

                    List jarFiles;
                    try
                    {
                        jarFiles = FileUtils.getFiles( archiveDirectory, includeList, excludeList );
                    }
                    catch ( IOException e )
                    {
                        throw new MojoExecutionException( "Failed to scan archive directory for JARs: "
                            + e.getMessage(), e );
                    }

                    for ( Iterator it = jarFiles.iterator(); it.hasNext(); )
                    {
                        File jarFile = (File) it.next();

                        processArchive( jarFile );
                        processed++;
                    }
                }
            }

            getLog().info( getMessage( "processed", new Integer( processed ) ) );
        }
        else
        {
            getLog().info( getMessage( "disabled", null ) );
        }
    }

    /**
     * Gets the {@code Commandline} to execute for a given Java archive taking a command line prepared for executing
     * jarsigner.
     *
     * @param archive The Java archive to get a {@code Commandline} to execute for.
     * @param commandLine A {@code Commandline} prepared for executing jarsigner without any arguments.
     *
     * @return A {@code Commandline} for executing jarsigner with {@code archive}.
     *
     * @throws NullPointerException if {@code archive} or {@code commandLine} is {@code null}.
     */
    protected abstract Commandline getCommandline( final File archive, final Commandline commandLine );

    /**
     * Gets a string representation of a {@code Commandline}.
     * 

This method creates the string representation by calling {@code commandLine.toString()} by default.

* * @param commandLine The {@code Commandline} to get a string representation of. * * @return The string representation of {@code commandLine}. * * @throws NullPointerException if {@code commandLine} is {@code null}. */ protected String getCommandlineInfo( final Commandline commandLine ) { if ( commandLine == null ) { throw new NullPointerException( "commandLine" ); } return commandLine.toString(); } /** * Checks whether the specified artifact is a ZIP file. * * @param artifact The artifact to check, may be null. * * @return true if the artifact looks like a ZIP file, false otherwise. */ private boolean isZipFile( final Artifact artifact ) { return artifact != null && artifact.getFile() != null && isZipFile( artifact.getFile() ); } /** * Checks whether the specified file is a JAR file. For our purposes, a ZIP file is a ZIP stream with at least one * entry. * * @param file The file to check, must not be null. * @return true if the file looks like a ZIP file, false otherwise. */ private boolean isZipFile( final File file ) { try { ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) ); try { return zis.getNextEntry() != null; } finally { zis.close(); } } catch ( Exception e ) { // ignore, will fail below } return false; } /** * Processes a given artifact. * * @param artifact The artifact to process. * @return true if the artifact is a JAR and was processed, false otherwise. * * @throws NullPointerException if {@code artifact} is {@code null}. * @throws MojoExecutionException if processing {@code artifact} fails. */ private boolean processArtifact( final Artifact artifact ) throws MojoExecutionException { if ( artifact == null ) { throw new NullPointerException( "artifact" ); } boolean processed = false; if ( isZipFile( artifact ) ) { processArchive( artifact.getFile() ); processed = true; } else { if ( this.verbose ) { getLog().info( getMessage( "unsupported", artifact ) ); } else if ( getLog().isDebugEnabled() ) { getLog().debug( getMessage( "unsupported", artifact ) ); } } return processed; } /** * Pre-processes a given archive. * * @param archive The archive to process, must not be null. * @throws MojoExecutionException If pre-processing failed. */ protected void preProcessArchive( final File archive ) throws MojoExecutionException { // default does nothing } /** * Processes a given archive. * * @param archive The archive to process. * @throws NullPointerException if {@code archive} is {@code null}. * @throws MojoExecutionException if processing {@code archive} fails. */ private void processArchive( final File archive ) throws MojoExecutionException { if ( archive == null ) { throw new NullPointerException( "archive" ); } preProcessArchive( archive ); if ( this.verbose ) { getLog().info( getMessage( "processing", archive ) ); } else if ( getLog().isDebugEnabled() ) { getLog().debug( getMessage( "processing", archive ) ); } Commandline commandLine = new Commandline(); commandLine.setExecutable( this.executable ); commandLine.setWorkingDirectory( this.project.getBasedir() ); if ( this.verbose ) { commandLine.createArg().setValue( "-verbose" ); } if ( StringUtils.isNotEmpty( maxMemory ) ) { commandLine.createArg().setValue( "-J-Xmx" + maxMemory ); } if ( this.arguments != null ) { commandLine.addArguments( this.arguments ); } commandLine = getCommandline( archive, commandLine ); try { if ( getLog().isDebugEnabled() ) { getLog().debug( getMessage( "command", getCommandlineInfo( commandLine ) ) ); } final int result = CommandLineUtils.executeCommandLine( commandLine, new InputStream() { public int read() { return -1; } }, new StreamConsumer() { public void consumeLine( final String line ) { if ( verbose ) { getLog().info( line ); } else { getLog().debug( line ); } } }, new StreamConsumer() { public void consumeLine( final String line ) { getLog().warn( line ); } } ); if ( result != 0 ) { throw new MojoExecutionException( getMessage( "failure", getCommandlineInfo( commandLine ), new Integer( result ) ) ); } } catch ( CommandLineException e ) { throw new MojoExecutionException( getMessage( "commandLineException", getCommandlineInfo( commandLine ) ), e ); } } /** * Locates the executable for the jarsigner tool. * * @return The executable of the jarsigner tool, never null. */ private String getExecutable() { String command = "jarsigner" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" ); String executable = findExecutable( command, System.getProperty( "java.home" ), new String[] { "../bin", "bin", "../sh" } ); if ( executable == null ) { try { Properties env = CommandLineUtils.getSystemEnvVars(); String[] variables = { "JDK_HOME", "JAVA_HOME" }; for ( int i = 0; i < variables.length && executable == null; i++ ) { executable = findExecutable( command, env.getProperty( variables[i] ), new String[] { "bin", "sh" } ); } } catch ( IOException e ) { if ( getLog().isDebugEnabled() ) { getLog().warn( "Failed to retrieve environment variables, cannot search for " + command, e ); } else { getLog().warn( "Failed to retrieve environment variables, cannot search for " + command ); } } } if ( executable == null ) { executable = command; } return executable; } /** * Finds the specified command in any of the given sub directories of the specified JDK/JRE home directory. * * @param command The command to find, must not be null. * @param homeDir The home directory to search in, may be null. * @param subDirs The sub directories of the home directory to search in, must not be null. * @return The (absolute) path to the command if found, null otherwise. */ private String findExecutable( String command, String homeDir, String[] subDirs ) { if ( StringUtils.isNotEmpty( homeDir ) ) { for ( int i = 0; i < subDirs.length; i++ ) { File file = new File( new File( homeDir, subDirs[i] ), command ); if ( file.isFile() ) { return file.getAbsolutePath(); } } } return null; } /** * Gets a message for a given key from the resource bundle backing the implementation. * * @param key The key of the message to return. * @param args Arguments to format the message with or {@code null}. * * @return The message with key {@code key} from the resource bundle backing the implementation. * * @throws NullPointerException if {@code key} is {@code null}. * @throws java.util.MissingResourceException if there is no message available matching {@code key} or accessing * the resource bundle fails. */ private String getMessage( final String key, final Object[] args ) { if ( key == null ) { throw new NullPointerException( "key" ); } return new MessageFormat( ResourceBundle.getBundle( "jarsigner" ).getString( key ) ).format( args ); } private String getMessage( final String key ) { return getMessage( key, null ); } private String getMessage( final String key, final Object arg ) { return getMessage( key, new Object[] { arg } ); } private String getMessage( final String key, final Object arg1, final Object arg2 ) { return getMessage( key, new Object[] { arg1, arg2 } ); } }