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

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

The newest version!
/*
 * 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.maven.plugins.jarsigner;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.jarsigner.JarSigner;
import org.apache.maven.shared.jarsigner.JarSignerRequest;
import org.apache.maven.shared.jarsigner.JarSignerUtil;
import org.apache.maven.shared.utils.ReaderFactory;
import org.apache.maven.shared.utils.StringUtils;
import org.apache.maven.shared.utils.cli.Commandline;
import org.apache.maven.shared.utils.cli.javatool.JavaToolException;
import org.apache.maven.shared.utils.io.FileUtils;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;

/**
 * Maven Jarsigner Plugin base class.
 *
 * @author Christian Schulte
 */
public abstract class AbstractJarsignerMojo extends AbstractMojo {

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.verbose", defaultValue = "false")
    private boolean verbose;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.keystore")
    private String keystore;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.storetype")
    private String storetype;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.storepass")
    private String storepass;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.providerName")
    private String providerName;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.providerClass")
    private String providerClass;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.providerArg")
    private String providerArg;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.alias")
    private String alias;

    /**
     * The maximum memory available to the JAR signer, e.g. 256M. See -Xmx for more details.
     */
    @Parameter(property = "jarsigner.maxMemory")
    private String maxMemory;

    /**
     * Archive to process. If set, neither the project artifact nor any attachments or archive sets are processed.
     */
    @Parameter(property = "jarsigner.archive")
    private File archive;

    /**
     * The base directory to scan for JAR files using Ant-like inclusion/exclusion patterns.
     *
     * @since 1.1
     */
    @Parameter(property = "jarsigner.archiveDirectory")
    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.
     *
     * @since 1.1
     */
    @Parameter
    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}.
     *
     * @since 1.1
     */
    @Parameter
    private String[] excludes = {};

    /**
     * List of additional arguments to append to the jarsigner command line. Each argument should be specified as a
     * separate element. For example, to specify the name of the signed jar, two elements are needed:
     * 
    *
  • Alternative using the command line: {@code -Djarsigner.arguments="-signedjar,my-project_signed.jar"}
  • *
  • Alternative using the Maven POM configuration:
  • *
*
     * {@code
     * 
     *   
     *     -signedjar
     *     my-project_signed.jar
     *   
     * 
     * }
*/ @Parameter(property = "jarsigner.arguments") private String[] arguments; /** * Set to {@code true} to disable the plugin. */ @Parameter(property = "jarsigner.skip", defaultValue = "false") private boolean skip; /** * Controls processing of the main artifact produced by the project. * * @since 1.1 */ @Parameter(property = "jarsigner.processMainArtifact", defaultValue = "true") private boolean processMainArtifact; /** * Controls processing of project attachments. If enabled, attached artifacts that are no JAR/ZIP files will be * automatically excluded from processing. * * @since 1.1 */ @Parameter(property = "jarsigner.processAttachedArtifacts", defaultValue = "true") private boolean processAttachedArtifacts; /** * Must be set to true if the password must be given via a protected * authentication path such as a dedicated PIN reader. * * @since 1.3 */ @Parameter(property = "jarsigner.protectedAuthenticationPath", defaultValue = "false") private boolean protectedAuthenticationPath; /** * 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. * * @since 1.2 */ @Parameter 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. * * @since 1.2 */ @Parameter private String[] excludeClassifiers; /** * The Maven project. */ @Parameter(defaultValue = "${project}", readonly = true, required = true) private MavenProject project; /** * The Maven settings. * * @since 1.5 */ @Parameter(defaultValue = "${settings}", readonly = true, required = true) private Settings settings; /** * Location of the working directory. * * @since 1.3 */ @Parameter(defaultValue = "${project.basedir}") private File workingDirectory; /** */ @Component private JarSigner jarSigner; /** * The current build session instance. This is used for * toolchain manager API calls. * * @since 1.3 */ @Parameter(defaultValue = "${session}", readonly = true, required = true) private MavenSession session; /** * To obtain a toolchain if possible. * * @since 1.3 */ @Component private ToolchainManager toolchainManager; /** * @since 1.3.2 */ @Component(hint = "mng-4384") private SecDispatcher securityDispatcher; @Override public final void execute() throws MojoExecutionException { if (this.skip) { getLog().info(getMessage("disabled")); return; } validateParameters(); Toolchain toolchain = getToolchain(); if (toolchain != null) { getLog().info("Toolchain in maven-jarsigner-plugin: " + toolchain); jarSigner.setToolchain(toolchain); } List archives = findJarfiles(); processArchives(archives); getLog().info(getMessage("processed", archives.size())); } /** * Finds all jar files, by looking at the Maven project and user configuration. * * @return a List of File objects * @throws MojoExecutionException if it was not possible to build a list of jar files */ private List findJarfiles() throws MojoExecutionException { if (this.archive != null) { // Only process this, but nothing more return Arrays.asList(this.archive); } List archives = new ArrayList<>(); if (processMainArtifact) { getFileFromArtifact(this.project.getArtifact()).ifPresent(archives::add); } if (processAttachedArtifacts) { 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 (Artifact artifact : this.project.getAttachedArtifacts()) { if (!includes.isEmpty() && !includes.contains(artifact.getClassifier())) { continue; } if (excludes.contains(artifact.getClassifier())) { continue; } getFileFromArtifact(artifact).ifPresent(archives::add); } } 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; try { archives.addAll(FileUtils.getFiles(archiveDirectory, includeList, excludeList)); } catch (IOException e) { throw new MojoExecutionException("Failed to scan archive directory for JARs: " + e.getMessage(), e); } } return archives; } /** * Creates the jar signer request to be executed. * * @param archive the archive file to treat by jarsigner * @return the request * @throws MojoExecutionException if an exception occurs * @since 1.3 */ protected abstract JarSignerRequest createRequest(File archive) throws MojoExecutionException; /** * 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"); } String commandLineInfo = commandLine.toString(); commandLineInfo = StringUtils.replace(commandLineInfo, this.storepass, "'*****'"); return commandLineInfo; } public String getStoretype() { return storetype; } public String getStorepass() { return storepass; } /** * 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 static boolean isZipFile(final Artifact artifact) { return artifact != null && artifact.getFile() != null && JarSignerUtil.isZipFile(artifact.getFile()); } /** * Examines an Artifact and extract the File object pointing to the Artifact jar file. * * @param artifact the artifact to examine * @return An Optional containing the File, or Optional.empty() if the File is not a jar file. * @throws NullPointerException if {@code artifact} is {@code null} */ private Optional getFileFromArtifact(final Artifact artifact) { if (artifact == null) { throw new NullPointerException("artifact"); } if (isZipFile(artifact)) { return Optional.of(artifact.getFile()); } if (this.verbose) { getLog().info(getMessage("unsupported", artifact)); } else if (getLog().isDebugEnabled()) { getLog().debug(getMessage("unsupported", artifact)); } return Optional.empty(); } /** * 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 implementation does nothing } /** * Validate the user supplied configuration/parameters. * * @throws MojoExecutionException if the user supplied configuration make further execution impossible */ protected void validateParameters() throws MojoExecutionException { // Default implementation does nothing } /** * Process (sign/verify) a list of archives. * * @param archives list of jar files to process * @throws MojoExecutionException if an error occurs during the processing of archives */ protected void processArchives(List archives) throws MojoExecutionException { for (File file : archives) { processArchive(file); } } /** * 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 */ protected final 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)); } JarSignerRequest request = createRequest(archive); request.setVerbose(verbose); request.setAlias(alias); request.setArchive(archive); request.setKeystore(keystore); request.setStoretype(storetype); request.setProviderArg(providerArg); request.setProviderClass(providerClass); request.setProviderName(providerName); request.setWorkingDirectory(workingDirectory); request.setMaxMemory(maxMemory); request.setProtectedAuthenticationPath(protectedAuthenticationPath); // Preserves 'file.encoding' the plugin is executed with. final List additionalArguments = new ArrayList<>(); boolean fileEncodingSeen = false; if (this.arguments != null) { for (final String argument : this.arguments) { if (argument.trim().startsWith("-J-Dfile.encoding=")) { fileEncodingSeen = true; } additionalArguments.add(argument); } } if (!fileEncodingSeen) { additionalArguments.add("-J-Dfile.encoding=" + ReaderFactory.FILE_ENCODING); } // Adds proxy information. if (this.settings != null && this.settings.getActiveProxy() != null && StringUtils.isNotEmpty(this.settings.getActiveProxy().getHost())) { additionalArguments.add( "-J-Dhttp.proxyHost=" + this.settings.getActiveProxy().getHost()); additionalArguments.add( "-J-Dhttps.proxyHost=" + this.settings.getActiveProxy().getHost()); additionalArguments.add( "-J-Dftp.proxyHost=" + this.settings.getActiveProxy().getHost()); if (this.settings.getActiveProxy().getPort() > 0) { additionalArguments.add( "-J-Dhttp.proxyPort=" + this.settings.getActiveProxy().getPort()); additionalArguments.add( "-J-Dhttps.proxyPort=" + this.settings.getActiveProxy().getPort()); additionalArguments.add( "-J-Dftp.proxyPort=" + this.settings.getActiveProxy().getPort()); } if (StringUtils.isNotEmpty(this.settings.getActiveProxy().getNonProxyHosts())) { additionalArguments.add("-J-Dhttp.nonProxyHosts=\"" + this.settings.getActiveProxy().getNonProxyHosts() + "\""); additionalArguments.add("-J-Dftp.nonProxyHosts=\"" + this.settings.getActiveProxy().getNonProxyHosts() + "\""); } } request.setArguments( !additionalArguments.isEmpty() ? additionalArguments.toArray(new String[additionalArguments.size()]) : null); // Special handling for passwords through the Maven Security Dispatcher request.setStorepass(decrypt(storepass)); try { executeJarSigner(jarSigner, request); } catch (JavaToolException e) { throw new MojoExecutionException(getMessage("commandLineException", e.getMessage()), e); } } /** * Executes jarsigner (execute signing or verification for a jar file). * * @param jarSigner the JarSigner execution interface * @param request the JarSignerRequest with parameters JarSigner should use * @throws JavaToolException if jarsigner could not be invoked * @throws MojoExecutionException if the invocation of jarsigner succeeded, but returned a non-zero exit code */ protected abstract void executeJarSigner(JarSigner jarSigner, JarSignerRequest request) throws JavaToolException, MojoExecutionException; protected String decrypt(String encoded) throws MojoExecutionException { try { return securityDispatcher.decrypt(encoded); } catch (SecDispatcherException e) { getLog().error("error using security dispatcher: " + e.getMessage(), e); throw new MojoExecutionException("error using security dispatcher: " + e.getMessage(), e); } } /** * 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 * @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 */ 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); } /** * the part with ToolchainManager lookup once we depend on * 2.0.9 (have it as prerequisite). Define as regular component field then. * hint: check maven-compiler-plugin code * * @return Toolchain instance */ private Toolchain getToolchain() { Toolchain tc = null; if (toolchainManager != null) { tc = toolchainManager.getToolchainFromBuildContext("jdk", session); } return tc; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy