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

org.apache.maven.archiver.MavenArchiver Maven / Gradle / Ivy

package org.apache.maven.archiver;

/*
 * 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 org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.jar.Manifest;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
import org.codehaus.plexus.interpolation.RecursionInterceptor;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.codehaus.plexus.interpolation.ValueSource;
import org.apache.maven.shared.utils.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author Emmanuel Venisse
 * @author kama
 * @version $Revision: 1741255 $ $Date: 2016-04-27 15:14:30 +0200 (Wed, 27 Apr 2016) $
 */
public class MavenArchiver
{

    /**
     * The simply layout.
     */
    public static final String SIMPLE_LAYOUT =
        "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";

    /**
     * Repository layout.
     */
    public static final String REPOSITORY_LAYOUT =
        "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
            + "${artifact.version}${dashClassifier?}.${artifact.extension}";

    /**
     * simple layout non unique.
     */
    public static final String SIMPLE_LAYOUT_NONUNIQUE =
        "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";

    /**
     * Repository layout non unique.
     */
    public static final String REPOSITORY_LAYOUT_NONUNIQUE =
        "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
            + "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";

    private static final List ARTIFACT_EXPRESSION_PREFIXES;

    static
    {
        List artifactExpressionPrefixes = new ArrayList();
        artifactExpressionPrefixes.add( "artifact." );

        ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
    }

    private JarArchiver archiver;

    private File archiveFile;

    /**
     * @param session The Maven Session.
     * @param project The Maven Project.
     * @param config The MavenArchiveConfiguration
     * @return The {@link Manifest}
     * @throws ManifestException In case of a failure.
     * @throws DependencyResolutionRequiredException Resolution failure.
     */
    public Manifest getManifest( MavenSession session, MavenProject project, MavenArchiveConfiguration config )
        throws ManifestException, DependencyResolutionRequiredException
    {
        boolean hasManifestEntries = !config.isManifestEntriesEmpty();
        Map entries =
            hasManifestEntries ? config.getManifestEntries() : Collections.emptyMap();

        Manifest manifest = getManifest( session, project, config.getManifest(), entries );

        // any custom manifest entries in the archive configuration manifest?
        if ( hasManifestEntries )
        {

            for ( Map.Entry entry : entries.entrySet() )
            {
                String key = entry.getKey();
                String value = entry.getValue();
                Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
                if ( key.equals( "Class-Path" ) && attr != null )
                {
                    // Merge the user-supplied Class-Path value with the programmatically
                    // created Class-Path. Note that the user-supplied value goes first
                    // so that resources there will override any in the standard Class-Path.
                    attr.setValue( value + " " + attr.getValue() );
                }
                else
                {
                    addManifestAttribute( manifest, key, value );
                }
            }
        }

        // any custom manifest sections in the archive configuration manifest?
        if ( !config.isManifestSectionsEmpty() )
        {
            for ( ManifestSection section : config.getManifestSections() )
            {
                Manifest.Section theSection = new Manifest.Section();
                theSection.setName( section.getName() );

                if ( !section.isManifestEntriesEmpty() )
                {
                    Map sectionEntries = section.getManifestEntries();

                    for ( Map.Entry entry : sectionEntries.entrySet() )
                    {
                        String key = entry.getKey();
                        String value = entry.getValue();
                        Manifest.Attribute attr = new Manifest.Attribute( key, value );
                        theSection.addConfiguredAttribute( attr );
                    }
                }

                manifest.addConfiguredSection( theSection );
            }
        }

        return manifest;
    }

    /**
     * Return a pre-configured manifest
     *
     * @param project {@link MavenProject}
     * @param config {@link ManifestConfiguration}
     * @return {@link Manifest}
     * @throws ManifestException Manifest exception.
     * @throws DependencyResolutionRequiredException Dependency resolution exception.
     */
    // TODO Add user attributes list and user groups list
    public Manifest getManifest( MavenProject project, ManifestConfiguration config )
        throws ManifestException, DependencyResolutionRequiredException
    {
        return getManifest( null, project, config, Collections.emptyMap() );
    }

    /**
     * @param mavenSession {@link MavenSession}
     * @param project {@link MavenProject}
     * @param config {@link ManifestConfiguration}
     * @return {@link Manifest}
     * @throws ManifestException The manifest exception.
     * @throws DependencyResolutionRequiredException The dependency resolution required exception.
     */
    public Manifest getManifest( MavenSession mavenSession, MavenProject project, ManifestConfiguration config )
        throws ManifestException, DependencyResolutionRequiredException
    {
        return getManifest( mavenSession, project, config, Collections.emptyMap() );
    }

    private void addManifestAttribute( Manifest manifest, Map map, String key, String value )
        throws ManifestException
    {
        if ( map.containsKey( key ) )
        {
            return; // The map value will be added later
        }
        addManifestAttribute( manifest, key, value );
    }

    private void addManifestAttribute( Manifest manifest, String key, String value )
        throws ManifestException
    {
        if ( !StringUtils.isEmpty( value ) )
        {
            Manifest.Attribute attr = new Manifest.Attribute( key, value );
            manifest.addConfiguredAttribute( attr );
        }
        else
        {
            // if the value is empty we have create an entry with an empty string
            // to prevent null print in the manifest file
            Manifest.Attribute attr = new Manifest.Attribute( key, "" );
            manifest.addConfiguredAttribute( attr );
        }
    }

    /**
     * @param session {@link MavenSession}
     * @param project {@link MavenProject}
     * @param config {@link ManifestConfiguration}
     * @param entries The entries.
     * @return {@link Manifest}
     * @throws ManifestException The manifest exception.
     * @throws DependencyResolutionRequiredException The dependency resolution required exception.
     */
    protected Manifest getManifest( MavenSession session, MavenProject project, ManifestConfiguration config,
                                    Map entries )
                                        throws ManifestException, DependencyResolutionRequiredException
    {
        // TODO: Should we replace "map" with a copy? Note, that we modify it!

        // Added basic entries
        Manifest m = new Manifest();
        addCreatedByEntry( session, m, entries );

        addCustomEntries( m, entries, config );

        if ( config.isAddClasspath() )
        {
            StringBuilder classpath = new StringBuilder();

            List artifacts = project.getRuntimeClasspathElements();
            String classpathPrefix = config.getClasspathPrefix();
            String layoutType = config.getClasspathLayoutType();
            String layout = config.getCustomClasspathLayout();

            Interpolator interpolator = new StringSearchInterpolator();

            for ( String artifactFile : artifacts )
            {
                File f = new File( artifactFile );
                if ( f.getAbsoluteFile().isFile() )
                {
                    Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );

                    if ( classpath.length() > 0 )
                    {
                        classpath.append( " " );
                    }
                    classpath.append( classpathPrefix );

                    // NOTE: If the artifact or layout type (from config) is null, give up and use the file name by
                    // itself.
                    if ( artifact == null || layoutType == null )
                    {
                        classpath.append( f.getName() );
                    }
                    else
                    {
                        List valueSources = new ArrayList();

                        handleExtraExpression( artifact, valueSources );

                        for ( ValueSource vs : valueSources )
                        {
                            interpolator.addValueSource( vs );
                        }

                        RecursionInterceptor recursionInterceptor =
                            new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );

                        try
                        {
                            if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE.equals( layoutType ) )
                            {
                                if ( config.isUseUniqueVersions() )
                                {
                                    classpath.append( interpolator.interpolate( SIMPLE_LAYOUT, recursionInterceptor ) );
                                }
                                else
                                {
                                    classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE,
                                                                                recursionInterceptor ) );
                                }
                            }
                            else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY.equals( layoutType ) )
                            {
                                // we use layout /$groupId[0]/../${groupId[n]/$artifactId/$version/{fileName}
                                // here we must find the Artifact in the project Artifacts to create the maven layout
                                if ( config.isUseUniqueVersions() )
                                {
                                    classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT,
                                                                                recursionInterceptor ) );
                                }
                                else
                                {
                                    classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE,
                                                                                recursionInterceptor ) );
                                }
                            }
                            else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM.equals( layoutType ) )
                            {
                                if ( layout == null )
                                {
                                    throw new ManifestException( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM
                                        + " layout type was declared, but custom layout expression was not"
                                        + " specified. Check your  element." );
                                }

                                classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
                            }
                            else
                            {
                                throw new ManifestException( "Unknown classpath layout type: '" + layoutType
                                    + "'. Check your  element." );
                            }
                        }
                        catch ( InterpolationException e )
                        {
                            ManifestException error =
                                new ManifestException( "Error interpolating artifact path for classpath entry: "
                                    + e.getMessage() );

                            error.initCause( e );
                            throw error;
                        }
                        finally
                        {
                            for ( ValueSource vs : valueSources )
                            {
                                interpolator.removeValuesSource( vs );
                            }
                        }
                    }
                }
            }

            if ( classpath.length() > 0 )
            {
                // Class-Path is special and should be added to manifest even if
                // it is specified in the manifestEntries section
                addManifestAttribute( m, "Class-Path", classpath.toString() );
            }
        }

        if ( config.isAddDefaultSpecificationEntries() )
        {
            handleSpecificationEntries( project, entries, m );
        }

        if ( config.isAddDefaultImplementationEntries() )
        {
            handleImplementationEntries( project, entries, m );
        }

        String mainClass = config.getMainClass();
        if ( mainClass != null && !"".equals( mainClass ) )
        {
            addManifestAttribute( m, entries, "Main-Class", mainClass );
        }

        if ( config.isAddExtensions() )
        {
            handleExtensions( project, entries, m );
        }

        return m;
    }

    private void handleExtraExpression( Artifact artifact, List valueSources )
    {
        valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact,
                                                         true ) );
        valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES,
                                                         artifact.getArtifactHandler(), true ) );

        Properties extraExpressions = new Properties();
        // FIXME: This query method SHOULD NOT affect the internal
        // state of the artifact version, but it does.
        if ( !artifact.isSnapshot() )
        {
            extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
        }

        extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
        if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
        {
            extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
            extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
        }
        else
        {
            extraExpressions.setProperty( "dashClassifier", "" );
            extraExpressions.setProperty( "dashClassifier?", "" );
        }
        valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES,
                                                             extraExpressions, true ) );
    }

    private void handleExtensions( MavenProject project, Map entries, Manifest m )
        throws ManifestException
    {
        // TODO: this is only for applets - should we distinguish them as a packaging?
        StringBuilder extensionsList = new StringBuilder();
        Set artifacts = (Set) project.getArtifacts();

        for ( Artifact artifact : artifacts )
        {
            if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
            {
                if ( "jar".equals( artifact.getType() ) )
                {
                    if ( extensionsList.length() > 0 )
                    {
                        extensionsList.append( " " );
                    }
                    extensionsList.append( artifact.getArtifactId() );
                }
            }
        }

        if ( extensionsList.length() > 0 )
        {
            addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
        }

        for ( Object artifact1 : artifacts )
        {
            // TODO: the correct solution here would be to have an extension type, and to read
            // the real extension values either from the artifact's manifest or some part of the POM
            Artifact artifact = (Artifact) artifact1;
            if ( "jar".equals( artifact.getType() ) )
            {
                String artifactId = artifact.getArtifactId().replace( '.', '_' );
                String ename = artifactId + "-Extension-Name";
                addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
                String iname = artifactId + "-Implementation-Version";
                addManifestAttribute( m, entries, iname, artifact.getVersion() );

                if ( artifact.getRepository() != null )
                {
                    iname = artifactId + "-Implementation-URL";
                    String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
                    addManifestAttribute( m, entries, iname, url );
                }
            }
        }
    }

    private void handleImplementationEntries( MavenProject project, Map entries, Manifest m )
        throws ManifestException
    {
        addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
        addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
        // MJAR-5
        addManifestAttribute( m, entries, "Implementation-Vendor-Id", project.getGroupId() );

        if ( project.getOrganization() != null )
        {
            addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
        }

        if ( project.getUrl() != null )
        {
            addManifestAttribute( m, entries, "Implementation-URL", project.getUrl() );
        }
    }

    private void handleSpecificationEntries( MavenProject project, Map entries, Manifest m )
        throws ManifestException
    {
        addManifestAttribute( m, entries, "Specification-Title", project.getName() );

        try
        {
            ArtifactVersion version = project.getArtifact().getSelectedVersion();
            String specVersion = String.format( "%s.%s", version.getMajorVersion(), version.getMinorVersion() );
            addManifestAttribute( m, entries, "Specification-Version", specVersion );
        }
        catch ( OverConstrainedVersionException e )
        {
            throw new ManifestException( "Failed to get selected artifact version to calculate"
                + " the specification version: " + e.getMessage() );
        }

        if ( project.getOrganization() != null )
        {
            addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
        }
    }

    private void addCustomEntries( Manifest m, Map entries, ManifestConfiguration config )
        throws ManifestException
    {
        addManifestAttribute( m, entries, "Built-By", System.getProperty( "user.name" ) );
        addManifestAttribute( m, entries, "Build-Jdk", System.getProperty( "java.version" ) );

        /*
         * TODO: rethink this, it wasn't working Artifact projectArtifact = project.getArtifact(); if (
         * projectArtifact.isSnapshot() ) { Manifest.Attribute buildNumberAttr = new Manifest.Attribute( "Build-Number",
         * "" + project.getSnapshotDeploymentBuildNumber() ); m.addConfiguredAttribute( buildNumberAttr ); }
         */
        if ( config.getPackageName() != null )
        {
            addManifestAttribute( m, entries, "Package", config.getPackageName() );
        }
    }

    /**
     * @return {@link JarArchiver}
     */
    public JarArchiver getArchiver()
    {
        return archiver;
    }

    /**
     * @param archiver {@link JarArchiver}
     */
    public void setArchiver( JarArchiver archiver )
    {
        this.archiver = archiver;
    }

    /**
     * @param outputFile Set output file.
     */
    public void setOutputFile( File outputFile )
    {
        archiveFile = outputFile;
    }

    /**
     * @param session {@link MavenSession}
     * @param project {@link MavenProject}
     * @param archiveConfiguration {@link MavenArchiveConfiguration}
     * @throws org.codehaus.plexus.archiver.ArchiverException Archiver Exception.
     * @throws ManifestException Manifest Exception.
     * @throws IOException IO Exception.
     * @throws DependencyResolutionRequiredException Dependency resolution exception.
     */
    public void createArchive( MavenSession session, MavenProject project,
                               MavenArchiveConfiguration archiveConfiguration )
                                   throws ManifestException, IOException,
                                   DependencyResolutionRequiredException
    {
        // we have to clone the project instance so we can write out the pom with the deployment version,
        // without impacting the main project instance...
        MavenProject workingProject = null;
        workingProject = (MavenProject) project.clone();

        boolean forced = archiveConfiguration.isForced();
        if ( archiveConfiguration.isAddMavenDescriptor() )
        {
            // ----------------------------------------------------------------------
            // We want to add the metadata for the project to the JAR in two forms:
            //
            // The first form is that of the POM itself. Applications that wish to
            // access the POM for an artifact using maven tools they can.
            //
            // The second form is that of a properties file containing the basic
            // top-level POM elements so that applications that wish to access
            // POM information without the use of maven tools can do so.
            // ----------------------------------------------------------------------

            if ( workingProject.getArtifact().isSnapshot() )
            {
                workingProject.setVersion( workingProject.getArtifact().getVersion() );
            }

            String groupId = workingProject.getGroupId();

            String artifactId = workingProject.getArtifactId();

            archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );

            // ----------------------------------------------------------------------
            // Create pom.properties file
            // ----------------------------------------------------------------------

            File customPomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
            File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
            File pomPropertiesFile = new File( dir, "pom.properties" );

            new PomPropertiesUtil().createPomProperties( session, workingProject, archiver,
                customPomPropertiesFile, pomPropertiesFile, forced );
        }

        // ----------------------------------------------------------------------
        // Create the manifest
        // ----------------------------------------------------------------------

        File manifestFile = archiveConfiguration.getManifestFile();

        if ( manifestFile != null )
        {
            archiver.setManifest( manifestFile );
        }

        Manifest manifest = getManifest( session, workingProject, archiveConfiguration );

        // Configure the jar
        archiver.addConfiguredManifest( manifest );

        archiver.setCompress( archiveConfiguration.isCompress() );

        archiver.setRecompressAddedZips( archiveConfiguration.isRecompressAddedZips() );

        archiver.setIndex( archiveConfiguration.isIndex() );

        archiver.setDestFile( archiveFile );

        // make the archiver index the jars on the classpath, if we are adding that to the manifest
        if ( archiveConfiguration.getManifest().isAddClasspath() )
        {
            List artifacts = project.getRuntimeClasspathElements();
            for ( String artifact : artifacts )
            {
                File f = new File( artifact );
                archiver.addConfiguredIndexJars( f );
            }
        }

        archiver.setForced( forced );
        if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
        {
            // TODO Should issue a warning here, but how do we get a logger?
            // TODO getLog().warn(
            // "Forced build is disabled, but disabling the forced mode isn't supported by the archiver." );
        }

        // create archive
        archiver.createArchive();
    }

    private void addCreatedByEntry( MavenSession session, Manifest m, Map entries )
        throws ManifestException
    {
        String createdBy = "Apache Maven";
        if ( session != null ) // can be null due to API backwards compatibility
        {
            String mavenVersion = session.getSystemProperties().getProperty( "maven.version" );
            if ( mavenVersion != null )
            {
                createdBy += " " + mavenVersion;
            }
        }
        addManifestAttribute( m, entries, "Created-By", createdBy );
    }

    private Artifact findArtifactWithFile( Set artifacts, File file )
    {
        for ( Artifact artifact : artifacts )
        {
            // normally not null but we can check
            if ( artifact.getFile() != null )
            {
                if ( artifact.getFile().equals( file ) )
                {
                    return artifact;
                }
            }
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy