org.ops4j.pax.construct.util.PomUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.construct.util.PomUtils.java

Source

package org.ops4j.pax.construct.util;

/*
 * Copyright 2007 Stuart McCulloch, Alin Dreghiciu
 *
 * 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 java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Repository;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

/**
 * Provide API {@link Pom} and factory for editing Maven project files
 */
public final class PomUtils {
    /**
     * Hide constructor for utility class
     */
    private PomUtils() {
        /*
         * nothing to do
         */
    }

    /**
     * API for editing Maven project files
     */
    public interface Pom {
        /**
         * @return unique project identifier
         */
        String getId();

        /**
         * @return parents' unique project identifier
         */
        String getParentId();

        /**
         * @return project group id
         */
        String getGroupId();

        /**
         * @return project artifact id
         */
        String getArtifactId();

        /**
         * @return project version
         */
        String getVersion();

        /**
         * @return project packaging
         */
        String getPackaging();

        /**
         * @return names of modules contained in this project
         */
        List getModuleNames();

        /**
         * @return the physical parent project
         */
        Pom getContainingPom();

        /**
         * @param module name of a module in this project
         * @return the module POM, null if it doesn't exist
         */
        Pom getModulePom(String module);

        /**
         * @return the underlying Maven POM file
         */
        File getFile();

        /**
         * @return the directory containing this Maven project
         */
        File getBasedir();

        /**
         * @return true if this is an OSGi bundle project, otherwise false
         */
        boolean isBundleProject();

        /**
         * @return the symbolic name for this project, null if it doesn't define one
         */
        String getBundleSymbolicName();

        /**
         * @param pom the new logical parent project
         * @param relativePath the relative path from this POM to its new parent
         * @param overwrite overwrite element if true, otherwise throw {@link ExistingElementException}
         * @throws ExistingElementException
         */
        void setParent(Pom pom, String relativePath, boolean overwrite) throws ExistingElementException;

        /**
         * @param project the new logical parent project
         * @param relativePath the relative path from this POM to the parent
         * @param overwrite overwrite element if true, otherwise throw {@link ExistingElementException}
         * @throws ExistingElementException
         */
        void setParent(MavenProject project, String relativePath, boolean overwrite)
                throws ExistingElementException;

        /**
         * @param newGroupId the new project group id
         */
        void setGroupId(String newGroupId);

        /**
         * @param newVersion the new project version
         */
        void setVersion(String newVersion);

        /**
         * @param repository a Maven repository
         * @param snapshots enable snapshots for this repository
         * @param releases enable releases for this repository
         * @param overwrite overwrite element if true, otherwise throw {@link ExistingElementException}
         * @param pluginRepo treat as plugin repository if true, otherwise assume normal repository
         * @throws ExistingElementException
         */
        void addRepository(Repository repository, boolean snapshots, boolean releases, boolean overwrite,
                boolean pluginRepo) throws ExistingElementException;

        /**
         * @param module module name
         * @param overwrite overwrite element if true, otherwise throw {@link ExistingElementException}
         * @throws ExistingElementException
         */
        void addModule(String module, boolean overwrite) throws ExistingElementException;

        /**
         * @param module module name
         * @return true if module was removed from the project, otherwise false
         */
        boolean removeModule(String module);

        /**
         * @param dependency project dependency
         * @param overwrite overwrite element if true, otherwise throw {@link ExistingElementException}
         * @throws ExistingElementException
         */
        void addDependency(Dependency dependency, boolean overwrite) throws ExistingElementException;

        /**
         * @param dependency project dependency
         * @param newGroupId updated dependency group id
         * @return true if the dependency was updated
         */
        boolean updateDependencyGroup(Dependency dependency, String newGroupId);

        /**
         * @param dependency project dependency
         * @return true if dependency was removed from the project, otherwise false
         */
        boolean removeDependency(Dependency dependency);

        /**
         * @param groupId dependency exclusion group id
         * @param artifactId dependency exclusion artifact id
         * @param overwrite overwrite element if true, otherwise throw {@link ExistingElementException}
         * @throws ExistingElementException
         */
        void addExclusion(String groupId, String artifactId, boolean overwrite) throws ExistingElementException;

        /**
         * @param groupId dependency exclusion group id
         * @param artifactId dependency exclusion artifact id
         * @return true if dependency exclusion was removed from the project, otherwise false
         */
        boolean removeExclusion(String groupId, String artifactId);

        /**
         * @return properties defined by the current project
         */
        Properties getProperties();

        /**
         * @param key property key
         * @param value property value
         */
        void setProperty(String key, String value);

        /**
         * @param groupId plugin group id
         * @param artifactId plugin artifact id
         * @param newVersion new plugin version
         * @return true if the plugin was updated
         */
        boolean updatePluginVersion(String groupId, String artifactId, String newVersion);

        /**
         * Merge a section of XML from another Maven project POM
         * 
         * @param pom another Maven project
         * @param fromSection path to XML section to merge from
         * @param toSection path to XML section to merge into
         * @param append when true, append instead of merging
         */
        void mergeSection(Pom pom, String fromSection, String toSection, boolean append);

        /**
         * Overlay POM template with detail from another Maven project POM
         * 
         * @param pom another Maven project
         */
        void overlayDetails(Pom pom);

        /**
         * @throws IOException
         */
        void write() throws IOException;
    }

    /**
     * Thrown when a POM element already exists and can't be overwritten {@link Pom}
     */
    public static class ExistingElementException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        /**
         * @param element name of the existing POM element
         */
        public ExistingElementException(String element) {
            super("Project already has a <" + element + "> which matches, use -Doverwrite or -o to replace it");
        }

        /**
         * {@inheritDoc}
         */
        public synchronized Throwable fillInStackTrace() {
            return this;
        }

        /**
         * {@inheritDoc}
         */
        public String toString() {
            return "[INFO] not available";
        }
    }

    /**
     * Factory method that provides an editor for an existing Maven project file
     * 
     * @param here a Maven POM, or a directory containing a file named 'pom.xml'
     * @return simple Maven project editor
     * @throws IOException
     */
    public static Pom readPom(File here) throws IOException {
        File candidate = here;

        if (null == here) {
            throw new IOException("null location");
        } else if (here.isDirectory()) {
            candidate = new File(here, "pom.xml");
        }

        return new XppPom(candidate);
    }

    /**
     * Factory method that provides an editor for a new Maven project file
     * 
     * @param here the file, or directory for the new Maven project
     * @param groupId project group id
     * @param artifactId project artifact id
     * @return simple Maven project editor
     * @throws IOException
     */
    public static Pom createModulePom(File here, String groupId, String artifactId) throws IOException {
        File candidate = here;

        if (null == here) {
            throw new IOException("null location");
        } else if (here.isDirectory()) {
            candidate = new File(here, "pom.xml");
        }

        return new XppPom(candidate, groupId, artifactId);
    }

    /**
     * @param project Maven project
     * @return true if this is an OSGi bundle project, otherwise false
     */
    public static boolean isBundleProject(MavenProject project) {
        return isBundleProject(project, null, null, null, false);
    }

    /**
     * @param project Maven project
     * @param resolver artifact resolver
     * @param remoteRepos sequence of remote repositories
     * @param localRepo local Maven repository
     * @param testMetadata check jar manifest for OSGi attributes if true
     * @return true if this is an OSGi bundle project, otherwise false
     */
    public static boolean isBundleProject(MavenProject project, ArtifactResolver resolver, List remoteRepos,
            ArtifactRepository localRepo, boolean testMetadata) {
        String packaging = project.getPackaging();
        if (packaging != null && packaging.indexOf("bundle") >= 0) {
            return true;
        }

        return isBundleArtifact(project.getArtifact(), resolver, remoteRepos, localRepo, testMetadata);
    }

    /**
     * @param artifact Maven artifact
     * @param resolver artifact resolver
     * @param remoteRepos sequence of remote repositories
     * @param localRepo local Maven repository
     * @param testMetadata check jar manifest for OSGi attributes if true
     * @return true if this is an OSGi bundle artifact, otherwise false
     */
    public static boolean isBundleArtifact(Artifact artifact, ArtifactResolver resolver, List remoteRepos,
            ArtifactRepository localRepo, boolean testMetadata) {
        if (null == artifact) {
            return false;
        }

        String type = artifact.getType();
        if (null != type && type.indexOf("bundle") >= 0) {
            return true;
        } else if (!testMetadata || !downloadFile(artifact, resolver, remoteRepos, localRepo)) {
            return false;
        }

        try {
            return isBundleArtifact(new JarFile(artifact.getFile()).getManifest());
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * @param manifest jar manifest, possibly null
     * @return true if this is an OSGi bundle artifact, otherwise false
     */
    private static boolean isBundleArtifact(Manifest manifest) {
        if (null == manifest) {
            return false;
        }

        Attributes mainAttributes = manifest.getMainAttributes();

        return mainAttributes.getValue("Bundle-SymbolicName") != null
                || mainAttributes.getValue("Bundle-Name") != null;
    }

    /**
     * Look for the artifact in local Maven repository
     * 
     * @param artifact Maven artifact
     * @param resolver artifact resolver
     * @param localRepo local Maven repository
     * @return true if the artifact is available, otherwise false
     */
    public static boolean getFile(Artifact artifact, ArtifactResolver resolver, ArtifactRepository localRepo) {
        return downloadFile(artifact, resolver, Collections.EMPTY_LIST, localRepo);
    }

    /**
     * Look for the artifact in local and remote Maven repositories
     * 
     * @param artifact Maven artifact
     * @param resolver artifact resolver
     * @param remoteRepos sequence of remote repositories
     * @param localRepo local Maven repository
     * @return true if the artifact is available, otherwise false
     */
    public static boolean downloadFile(Artifact artifact, ArtifactResolver resolver, List remoteRepos,
            ArtifactRepository localRepo) {
        if (artifact.getFile() == null || !artifact.getFile().exists()) {
            try {
                resolver.resolve(artifact, remoteRepos, localRepo);
            } catch (AbstractArtifactResolutionException e) {
                return false;
            } catch (NullPointerException e) {
                return false;
            }
        }

        return true;
    }

    /**
     * Try to combine overlapping group and artifact identifiers to remove duplicate elements
     * 
     * @param groupId project group id
     * @param artifactId project artifact id
     * @return the combined group and artifact sequence
     */
    public static String getCompoundId(String groupId, String artifactId) {
        // treat any dashes like dots
        String lhs = groupId.replace('-', '.');
        String rhs = artifactId.replace('-', '.');

        // simple common prefix check
        if (rhs.equals(lhs) || rhs.startsWith(lhs + '.')) {
            return artifactId;
        }

        rhs = '.' + rhs; // optimization when testing for overlap

        // check for overlapping segments by repeated chopping of artifactId
        for (int i = rhs.length(); i > 0; i = rhs.lastIndexOf('.', i - 1)) {
            if (lhs.endsWith(rhs.substring(0, i))) {
                if (rhs.length() == i) {
                    return groupId;
                }

                return groupId + '.' + artifactId.substring(i);
            }
        }

        // no common segments, so append
        return groupId + '.' + artifactId;
    }

    /**
     * Find the symbolic (meta) Maven version, such as 1.0-SNAPSHOT
     * 
     * @param artifact Maven artifact
     * @return meta version for the artifact
     */
    public static String getMetaVersion(Artifact artifact) {
        if (artifact.isSnapshot()) {
            try {
                return artifact.getSelectedVersion().toString();
            } catch (OverConstrainedVersionException e) {
                return artifact.getVersion();
            } catch (NullPointerException e) {
                return artifact.getVersion();
            }
        }

        return artifact.getVersion();
    }

    /**
     * @param version project version
     * @return true if we need to resolve this version
     */
    public static boolean needReleaseVersion(String version) {
        return isEmpty(version) || "RELEASE".equals(version) || "LATEST".equals(version);
    }

    /**
     * @param artifact Maven artifact
     * @param source metadata source
     * @param remoteRepos sequence of remote repositories
     * @param localRepo local Maven repository
     * @param range acceptable versions
     * @return the release version if available, otherwise throws {@link MojoExecutionException}
     * @throws MojoExecutionException
     */
    public static String getReleaseVersion(Artifact artifact, ArtifactMetadataSource source, List remoteRepos,
            ArtifactRepository localRepo, VersionRange range) throws MojoExecutionException {
        try {
            List versions = source.retrieveAvailableVersions(artifact, localRepo, remoteRepos);
            ArtifactVersion releaseVersion = getLatestReleaseInRange(versions, range);
            if (null == releaseVersion) {
                throw new MojoExecutionException("Unable to find release version for " + artifact);
            }
            return releaseVersion.toString();
        } catch (ArtifactMetadataRetrievalException e) {
            throw new MojoExecutionException("Unable to find artifact " + artifact);
        }
    }

    /**
     * @param versions list of available versions
     * @param range acceptable range of versions
     * @return latest acceptable release, otherwise null
     */
    private static ArtifactVersion getLatestReleaseInRange(List versions, VersionRange range) {
        final ArtifactVersion baseline = new DefaultArtifactVersion("0");

        ArtifactVersion releaseVersion = baseline;
        for (Iterator i = versions.iterator(); i.hasNext();) {
            ArtifactVersion v = (ArtifactVersion) i.next();
            if (isCompatible(range, v) && releaseVersion.compareTo(v) <= 0) {
                releaseVersion = v;
            }
        }

        // no compatible version found
        if (baseline == releaseVersion) {
            return null;
        }

        return releaseVersion;
    }

    /**
     * @param range compatible range
     * @param version candidate version
     * @return true if this version is compatible, otherwise false
     */
    private static boolean isCompatible(VersionRange range, ArtifactVersion version) {
        if (version.getMajorVersion() > 10000000 || ArtifactUtils.isSnapshot(version.toString())) {
            return false; // ignore snapshots and possible timestamped releases
        } else if (range != null && !range.containsVersion(version)) {
            return false; // ignore this version as it's not compatible
        }

        return true;
    }

    /**
     * @param param Maven plugin parameter
     * @return true if parameter is empty, otherwise false
     */
    public static boolean isEmpty(String param) {
        return null == param || param.trim().length() == 0;
    }

    /**
     * @param param Maven plugin parameter
     * @return false if parameter is empty, otherwise true
     */
    public static boolean isNotEmpty(String param) {
        return null != param && param.trim().length() > 0;
    }
}