blob: fa88841bccc75018f5bb7dd250536e56e12195fd [file] [log] [blame]
/*
* 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.jackrabbit.filevault.maven.packaging;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.PackageInfo;
import org.apache.jackrabbit.vault.packaging.VersionRange;
import org.apache.jackrabbit.vault.packaging.impl.DefaultPackageInfo;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class represents a dependency to another content package.
* A dependency consists of a group name, a package name and a version range.
* <p>
* A dependency is declared as a {@code <dependency>} element of a list
* style {@code <dependencies>} element:
* <pre>
* &lt;dependency&gt;
* &lt;group&gt;theGroup&lt;/group&gt;
* &lt;name&gt;theName&lt;/name&gt;
* &lt;version&gt;1.5&lt;/version&gt;
* &lt;/dependency&gt;
* </pre>
* <p>
* The dependency can also reference a maven project dependency, this is preferred
* as it yields to more robust builds.
* <pre>
* &lt;dependency&gt;
* &lt;groupId&gt;theGroup&lt;/groupId&gt;
* &lt;artifactId&gt;theName&lt;/artifactId&gt;
* &lt;/dependency&gt;
* </pre>
* <p>
* The {@code versionRange} may be indicated as a single version, in which
* case the version range has no upper bound and defines the minimal version
* accepted. Otherwise, the version range defines a lower and upper bound of
* accepted versions, where the bounds are either included using parentheses
* {@code ()} or excluded using brackets {@code []}
*/
public class MavenBasedPackageDependency {
/**
* The group name, required for package-id references
*/
private String group;
/**
* The group id, required for maven coordinate references
*/
private String groupId;
/**
* The package name, required for package-id references.
*/
private String name;
/**
* The artifact id, required for maven coordinate references
*/
private String artifactId;
/**
* The classifier, optional for maven coordinate references
*/
private String classifier;
/**
* The version range (optional)
*/
private String version;
/**
* Package information of this dependency. Only available for maven referenced dependencies after {@link #resolve(MavenProject, Log)}
*/
private PackageInfo info;
/**
* Resolved package dependency. Only available after {@link #resolve(MavenProject, Log)}
*/
private org.apache.jackrabbit.vault.packaging.Dependency dependency;
private boolean isResolved;
private String mavenVersion;
private final static String MAVEN_REPOSITORY_SCHEME = "maven";
// default constructor for passing Maven Mojo parameters of that type
public MavenBasedPackageDependency() {
}
/**
* Converts a list of {@link MavenBasedPackageDependency} instances to vault dependencies.
*
* @param project the Maven project
* @param log the Logger
* @param dependencies The list of {@link MavenBasedPackageDependency} instances to convert.
* @throws IOException in case meta information could not be read from the project dependency or the
* dependency is not a content package.
*/
public static void resolve(final MavenProject project, final Log log, final Collection<MavenBasedPackageDependency> dependencies) throws IOException {
for (MavenBasedPackageDependency dependency : dependencies) {
dependency.resolve(project, log);
}
}
/**
* Helper method for {@link #toString} to convert an instance of this
* class to a Vault Packaging Dependency for easy string conversion.
* @throws IOException in case meta information could not be read from the project dependency or the
* dependency is not a content package.
* @throws URISyntaxException
*/
@SuppressWarnings("deprecation")
private void resolve(final MavenProject project, final Log log) throws IOException {
if (isResolved) {
return;
} else {
isResolved = true;
}
if (!StringUtils.isEmpty(group) || !StringUtils.isEmpty(name)) {
log.warn("Using package id in dependencies is deprecated. Use Maven coordinates (given via 'groupId' and 'artifactId') instead of '" + group + ":" + name +"'!");
}
if (!StringUtils.isEmpty(groupId) && !StringUtils.isEmpty(artifactId)) {
boolean foundMavenDependency = false;
if (project != null) {
for (Artifact a : project.getDependencyArtifacts()) {
if (a.getArtifactId().equals(artifactId) && a.getGroupId().equals(groupId) && StringUtils.equals(a.getClassifier(), classifier)) {
// check if file exists and if it points to a real file (might also point to a classes dir)
try {
readMetaData(a.getFile(), log);
mavenVersion = a.getVersion();
foundMavenDependency = true;
} catch (IOException e) {
// can not resolve name and group
log.warn("Could not resolve dependency '" + this + "'", e);
return;
}
break;
}
}
if (!foundMavenDependency) {
throw new IOException("Specified dependency '" + this + "' was not found among the Maven dependencies of this project!");
}
} else {
log.warn("Dependency '" + this + "' was given via Maven coordinates but there is no Maven project connected which allows to resolve those.");
return;
}
}
if (StringUtils.isEmpty(group) || StringUtils.isEmpty(name)) {
throw new IOException("Specified dependency " + this + " is not qualified (group and name or groupId and artifactId is missing)!");
}
VersionRange range = StringUtils.isEmpty(version) ? VersionRange.INFINITE : VersionRange.fromString(version);
dependency = new org.apache.jackrabbit.vault.packaging.Dependency(group, name, range);
}
public void readMetaData(File file, Log log) throws IOException {
PackageInfo info;
if (file.isDirectory()) {
log.info("Trying to extract package info from folder '" + file + "' as no package is availabe for the given dependency " + this);
info = DefaultPackageInfo.read(file);
if (info == null) {
// fallback to work dir
File fallbackDirectory = new File(file.getParent(), "vault-work");
log.info("Trying to extract package info from fallback folder '" + fallbackDirectory + "' as no package is availabe for the given dependency " + this);
info = DefaultPackageInfo.read(fallbackDirectory);
}
} else {
info = DefaultPackageInfo.read(file);
}
if (info != null) {
PackageId id = info.getId();
group = id.getGroup();
name = id.getName();
if (StringUtils.isEmpty(version)) {
log.debug("No explicit version range given for dependency '" + this+ "'. Using default version range derived from the Maven dependency");
version = new VersionRange(id.getVersion(), true, null, false).toString();
}
this.info = info;
} else {
throw new IOException("Dependency at '" + file + "' does not contain mandatory metadata for a content-package");
}
}
/**
* Returns the package info or {@code null}.
*
* @return the info.
*/
@Nullable
public PackageInfo getInfo() {
return info;
}
/**
* Returns the package dependency or {@code null} if not resolved.
*
* @return the package dependency.
*/
@Nullable
public org.apache.jackrabbit.vault.packaging.Dependency getPackageDependency() {
return dependency;
}
@Nullable
public URI getLocation() {
if (groupId != null && artifactId != null && mavenVersion != null) {
// which version?
return mavenCoordinatesToUri(groupId, artifactId, mavenVersion, classifier);
}
return null;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Dependency{");
sb.append("group='").append(group).append('\'');
sb.append(", groupId='").append(groupId).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", artifactId='").append(artifactId).append('\'');
sb.append(", version='").append(version).append('\'');
sb.append('}');
return sb.toString();
}
public static URI mavenCoordinatesToUri(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, String classifier) {
StringBuilder ssp = new StringBuilder();
ssp.append(groupId).append(":").append(artifactId).append(":").append(version).append(":zip");
if (StringUtils.isNotEmpty(classifier)) {
ssp.append(":").append(classifier);
}
try {
return new URI(MAVEN_REPOSITORY_SCHEME, ssp.toString(), null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Could not create uri from string " + ssp.toString(), e);
}
}
public static Artifact uriToMavenCoordinates(URI uri) {
if (!MAVEN_REPOSITORY_SCHEME.equals(uri.getScheme())) {
return null;
}
if (!uri.isOpaque()) {
throw new IllegalArgumentException("Only opaque Maven URIs are supported");
}
// support groupId, artifactId, packaging and classifier (format like https://maven.apache.org/plugins/maven-dependency-plugin/get-mojo.html#artifact)
// extract group id and artifact id
String[] parts = uri.getSchemeSpecificPart().split(":");
if (parts.length < 2) {
throw new IllegalArgumentException("At least group id and artifact id need to be given separatedby ':'");
}
String groupId = parts[0];
String artifactId = parts[1];
String version = "default";
if (parts.length > 2) {
version = parts[2];
}
String type = "zip";
if (parts.length > 3) {
type = parts[3];
}
String classifier = "";
if (parts.length > 4) {
type = parts[4];
}
// TODO: version must not be null!
return new DefaultArtifact(groupId, artifactId, version, null, type, classifier, null);
}
}