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; } }