blob: 69011191bbb65c9d3112aa66b647ab6ba7f2dc19 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2013 The University of Manchester
*
* Modifications to the initial code base are copyright of their
* respective authors, or their employers as appropriate.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package net.sf.taverna.t2.maven.plugins;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.DefaultDependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionException;
import org.apache.maven.project.DependencyResolutionResult;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.util.filter.ScopeDependencyFilter;
import uk.org.taverna.commons.profile.xml.jaxb.BundleInfo;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Constants;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
/**
* @author David Withers
*/
public class MavenOsgiUtils {
private final MavenProject project;
private final RepositorySystemSession repositorySystemSession;
private final ProjectDependenciesResolver projectDependenciesResolver;
private final Log log;
private Set<String> javaPackages;
private Set<String> systemPackages;
public MavenOsgiUtils(MavenProject project, RepositorySystemSession repositorySystemSession,
ProjectDependenciesResolver projectDependenciesResolver, Log log) {
this(project, repositorySystemSession, projectDependenciesResolver, new HashSet<String>(),
log);
}
public MavenOsgiUtils(MavenProject project, RepositorySystemSession repositorySystemSession,
ProjectDependenciesResolver projectDependenciesResolver, Set<String> systemPackages,
Log log) {
this.project = project;
this.repositorySystemSession = repositorySystemSession;
this.projectDependenciesResolver = projectDependenciesResolver;
this.systemPackages = systemPackages;
this.log = log;
javaPackages = Utils.getJavaPackages(log);
}
public Set<BundleArtifact> getBundleDependencies(String... scopes)
throws MojoExecutionException {
ScopeDependencyFilter scopeFilter = new ScopeDependencyFilter(Arrays.asList(scopes), null);
DefaultDependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(
project, repositorySystemSession);
dependencyResolutionRequest.setResolutionFilter(scopeFilter);
DependencyResolutionResult dependencyResolutionResult;
try {
dependencyResolutionResult = projectDependenciesResolver
.resolve(dependencyResolutionRequest);
} catch (DependencyResolutionException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
DependencyNode dependencyGraph = dependencyResolutionResult.getDependencyGraph();
if (dependencyGraph != null) {
checkBundleDependencies(dependencyGraph.getChildren());
return getBundleArtifacts(dependencyGraph.getChildren());
} else {
return new HashSet<BundleArtifact>();
}
}
public Set<BundleArtifact> getBundleArtifacts(List<DependencyNode> nodes) {
Set<BundleArtifact> bundleArtifacts = new HashSet<BundleArtifact>();
for (DependencyNode node : nodes) {
Artifact artifact = RepositoryUtils.toArtifact(node.getDependency().getArtifact());
String symbolicName = getManifestAttribute(artifact, Constants.BUNDLE_SYMBOLICNAME);
if (symbolicName != null) {
String version = getManifestAttribute(artifact, Constants.BUNDLE_VERSION);
bundleArtifacts.add(new BundleArtifact(artifact, symbolicName, version));
bundleArtifacts.addAll(getBundleArtifacts(node.getChildren()));
} else {
log.warn("Not an OSGi bundle : " + artifact.getId());
}
}
return bundleArtifacts;
}
public Set<Artifact> getAllArtifacts(List<DependencyNode> nodes) {
Set<Artifact> artifacts = new HashSet<Artifact>();
for (DependencyNode node : nodes) {
Artifact artifact = RepositoryUtils.toArtifact(node.getDependency().getArtifact());
if (isBundle(artifact)) {
artifacts.add(artifact);
artifacts.addAll(getAllArtifacts(node.getChildren()));
}
}
return artifacts;
}
public Set<Artifact> getArtifacts(List<DependencyNode> nodes) {
Set<Artifact> artifacts = new HashSet<Artifact>();
for (DependencyNode node : nodes) {
Artifact artifact = RepositoryUtils.toArtifact(node.getDependency().getArtifact());
if (isBundle(artifact)) {
artifacts.add(artifact);
}
}
return artifacts;
}
public List<BundleInfo> getBundles(Set<BundleArtifact> bundleDependencies)
throws MojoExecutionException {
List<BundleInfo> bundles = new ArrayList<BundleInfo>();
for (BundleArtifact bundleArtifact : bundleDependencies) {
Artifact artifact = bundleArtifact.getArtifact();
BundleInfo bundle = new BundleInfo();
bundle.setSymbolicName(bundleArtifact.getSymbolicName());
bundle.setVersion(bundleArtifact.getVersion());
bundle.setFileName(new File(artifact.getGroupId(), artifact.getFile().getName())
.getPath());
bundles.add(bundle);
}
Collections.sort(bundles, new BundleComparator());
return bundles;
}
public Map<String, Set<PackageVersion>> getPackageDependencies(List<DependencyNode> nodes) {
Map<String, Set<PackageVersion>> exportVersions = calculatePackageVersions(
getAllArtifacts(nodes), true, false);
return getPackageDependencies(getArtifacts(nodes), exportVersions);
}
public Map<String, Set<PackageVersion>> getPackageDependencies(Set<Artifact> requiredArtifacts,
Map<String, Set<PackageVersion>> exportVersions) {
Map<String, Set<PackageVersion>> importVersions = calculatePackageVersions(
requiredArtifacts, false, true);
Set<Artifact> newRequiredArtifacts = new HashSet<Artifact>();
for (Entry<String, Set<PackageVersion>> entry : importVersions.entrySet()) {
if (!javaPackages.contains(entry.getKey())) {
String packageName = entry.getKey();
Set<PackageVersion> importsVersions = entry.getValue();
Set<PackageVersion> exportsVersions = exportVersions.get(packageName);
if (exportVersions.containsKey(entry.getKey())) {
for (Artifact artifact : getRequiredArtifacts(importsVersions, exportsVersions)) {
if (!requiredArtifacts.contains(artifact)) {
newRequiredArtifacts.add(artifact);
}
}
}
}
}
if (!newRequiredArtifacts.isEmpty()) {
newRequiredArtifacts.addAll(requiredArtifacts);
return getPackageDependencies(newRequiredArtifacts, exportVersions);
}
return importVersions;
}
/**
* Returns the minimum set of artifacts required to satisfy the range of importVersions.
*
* @param importsVersions
* the version ranges required for the package
* @param exportVersions
* the available versions for the package
* @return the minimum set of artifacts required to satisfy the range of importVersions
*/
private Set<Artifact> getRequiredArtifacts(Set<PackageVersion> importsVersions,
Set<PackageVersion> exportVersions) {
Set<Artifact> requiredArtifacts = new HashSet<Artifact>();
List<Set<PackageVersion>> exportsSubsets = Utils.getSubsets(exportVersions);
for (Set<PackageVersion> exportsSubset : exportsSubsets) {
if (satisfiesImports(importsVersions, exportsSubset)) {
for (PackageVersion exportVersion : exportsSubset) {
requiredArtifacts.add(exportVersion.getArtifact());
}
break;
}
}
return requiredArtifacts;
}
private boolean satisfiesImports(Set<PackageVersion> imports, Set<PackageVersion> exports) {
for (PackageVersion importVersion : imports) {
if (!satisfiesImport(importVersion, exports)) {
return false;
}
}
return true;
}
private boolean satisfiesImport(PackageVersion importVersion, Set<PackageVersion> exports) {
for (PackageVersion exportVersion : exports) {
if (importVersion.getVersionRange().includes(exportVersion.getVersionRange().getLow())) {
return true;
}
}
return false;
}
public void checkBundleDependencies(List<DependencyNode> nodes) {
Map<Artifact, Set<Package>> unresolvedArtifacts = new HashMap<Artifact, Set<Package>>();
Set<Artifact> artifacts = getAllArtifacts(nodes);
Map<String, Set<PackageVersion>> exports = calculatePackageVersions(artifacts, true, false);
for (Artifact artifact : artifacts) {
if (isBundle(artifact)) {
Parameters imports = getManifestAttributeParameters(artifact,
Constants.IMPORT_PACKAGE);
if (imports != null) {
for (String packageName : imports.keySet()) {
boolean exportMissing = true;
VersionRange importRange = null;
Attrs attrs = imports.get(packageName);
if (isOptional(attrs)) {
exportMissing = false;
} else if (javaPackages.contains(packageName)
|| systemPackages.contains(packageName)
|| packageName.startsWith("org.osgi.")) {
exportMissing = false;
} else if (attrs == null || attrs.get(Constants.VERSION_ATTRIBUTE) == null) {
if (exports.containsKey(packageName)) {
exportMissing = false;
}
} else {
importRange = getVersionRange(attrs);
if (exports.containsKey(packageName)) {
for (PackageVersion exportVersion : exports.get(packageName)) {
if (importRange.includes(exportVersion.getVersionRange()
.getLow())) {
exportMissing = false;
break;
}
}
}
}
if (exportMissing) {
if (!unresolvedArtifacts.containsKey(artifact)) {
unresolvedArtifacts.put(artifact, new HashSet<Package>());
}
unresolvedArtifacts.get(artifact).add(
new Package(packageName, importRange));
}
}
}
}
}
for (Entry<Artifact, Set<Package>> unresolvedArtifact : unresolvedArtifacts.entrySet()) {
log.warn("Bundle : " + unresolvedArtifact.getKey().getId()
+ " has unresolved package dependencies:");
for (Package unresolvedPackage : unresolvedArtifact.getValue()) {
log.warn(" " + unresolvedPackage);
}
}
}
public Map<String, Set<PackageVersion>> calculatePackageVersions(Set<Artifact> artifacts,
boolean export, boolean unique) {
Map<String, Set<PackageVersion>> packageVersions = new HashMap<String, Set<PackageVersion>>();
for (Artifact artifact : artifacts) {
if (isBundle(artifact)) {
Parameters packages = getManifestAttributeParameters(artifact,
export ? Constants.EXPORT_PACKAGE : Constants.IMPORT_PACKAGE);
if (packages != null) {
for (String packageName : packages.keySet()) {
if (!packageVersions.containsKey(packageName)) {
packageVersions.put(packageName, new HashSet<PackageVersion>());
}
Set<PackageVersion> versions = packageVersions.get(packageName);
VersionRange versionRange = getVersionRange(packages.get(packageName));
boolean optional = isOptional(packages.get(packageName));
if (unique) {
// check if this version range is unique
boolean uniqueVersion = true;
for (PackageVersion version : versions) {
if (equals(versionRange, version.getVersionRange())) {
uniqueVersion = false;
break;
}
}
if (uniqueVersion) {
versions.add(new PackageVersion(versionRange, artifact, optional));
}
} else {
versions.add(new PackageVersion(versionRange, artifact, optional));
}
}
}
}
}
return packageVersions;
}
public Version getVersion(Attrs attrs) {
if (attrs == null) {
return Version.LOWEST;
}
return Version.parseVersion(attrs.get(Constants.VERSION_ATTRIBUTE));
}
public VersionRange getVersionRange(Attrs attrs) {
if (attrs == null) {
return new VersionRange("0");
}
String version = attrs.get(Constants.VERSION_ATTRIBUTE);
if (version == null) {
return new VersionRange("0");
}
return new VersionRange(version);
}
public boolean isBundle(Artifact artifact) {
return getManifestAttribute(artifact, Constants.BUNDLE_SYMBOLICNAME) != null;
}
public boolean isOptional(Attrs attrs) {
return attrs != null && "optional".equals(attrs.get(Constants.RESOLUTION_DIRECTIVE));
}
public boolean equals(VersionRange v1, VersionRange v2) {
return v1 == v2 || v1.toString().equals(v2.toString());
}
public Parameters getManifestAttributeParameters(Artifact artifact, String attributeName) {
String attributeValue = getManifestAttribute(artifact, attributeName);
if (attributeValue != null) {
return OSGiHeader.parseHeader(attributeValue);
}
return null;
}
public String getManifestAttribute(Artifact artifact, String attributeName) {
Manifest manifest = getManifest(artifact);
if (manifest != null) {
Attributes mainAttributes = manifest.getMainAttributes();
return mainAttributes.getValue(attributeName);
}
return null;
}
public Manifest getManifest(Artifact artifact) {
if (artifact != null) {
File file = artifact.getFile();
if (file != null) {
try {
JarFile jarFile = new JarFile(artifact.getFile());
return jarFile.getManifest();
} catch (IOException e) {
return null;
}
}
}
return null;
}
private final class BundleComparator implements Comparator<BundleInfo> {
@Override
public int compare(BundleInfo bundle1, BundleInfo bundle2) {
return bundle1.getSymbolicName().compareTo(bundle2.getSymbolicName());
}
}
// public static void main(String[] args) throws Exception {
// MavenOsgiUtils mavenOsgiUtils = new MavenOsgiUtils();
// Parameters exports = mavenOsgiUtils.getImports(new
// File("/Users/david/Documents/workspace-trunk/taverna-plugin-impl/target/taverna-plugin-impl-0.1.0-SNAPSHOT.jar"));
// for (String key : exports.keySet()) {
// System.out.println(key + " " + mavenOsgiUtils.getVersionRange(exports.get(key)));
// }
// }
}