blob: 596aabacac65e89827b70c989c40e6a5c8f73fb4 [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.cauldron.bld.ivy;
import static java.lang.String.format;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.url.URLResource;
import org.apache.ivy.plugins.resolver.BasicResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.FileUtil;
import org.cauldron.bld.config.BldFactory;
import org.cauldron.bld.core.internal.model.osgi.RequiredBundle;
import org.cauldron.sigil.model.IModelElement;
import org.cauldron.sigil.model.common.VersionRange;
import org.cauldron.sigil.model.eclipse.ISigilBundle;
import org.cauldron.sigil.model.osgi.IBundleModelElement;
import org.cauldron.sigil.repository.IResolution;
import org.cauldron.sigil.repository.ResolutionException;
/**
* This resolver is able to work with Sigil repositories.
* It does not allow publishing.
*/
public class SigilResolver extends BasicResolver implements IBldResolver {
/** the sigil-injected dependency organisation name */
static final String ORG_SIGIL = "sigil";
private IBldResolver resolver;
private String config;
private boolean extractBCP = false;
private Map<String, Resource> resourcesCache = new HashMap<String, Resource>();
/**
* no-args constructor required by Ivy.
*
* XXX: It doesn't currently seem to matter that Ivy instantiates this many times,
* since SigilParser caches it, and the ProjectRepositoryProvider static cache
* prevents it being re-loaded unnecessarily.
*
* If this should become a problem, then we will need to delegate to a singleton instance,
* as we have done in SigilParser.
*/
public SigilResolver() {
}
public void setConfig(String config) {
this.config = config;
}
/**
* if true, resolver extracts any jars embedded in Bundle-ClassPath.
*/
public void setExtractBCP(String extract) {
this.extractBCP = Boolean.parseBoolean(extract);
}
private IBldResolver getResolver() {
if (resolver == null) {
if (config == null) {
throw new Error("SigilResolver: not configured. Specify config=\"PATH\" in ivysettings.xml.");
}
try {
URI uri;
File file = new File(config);
if (file.isAbsolute()) {
uri = file.toURI();
} else {
URI cwd = new File(".").toURI();
uri = cwd.resolve(config);
}
Map<String, Properties> repositories = BldFactory.getConfig(uri).getRepositoryConfig();
resolver = new BldResolver(repositories);
} catch (IOException e) {
throw new Error("SigilResolver: failed to configure: " + e);
}
}
return resolver;
}
public IResolution resolveOrFail(IModelElement element, boolean transitive) throws ResolutionException {
return getResolver().resolveOrFail(element, transitive);
}
public IResolution resolve(IModelElement element, boolean transitive) {
return getResolver().resolve(element, transitive);
}
public String getTypeName() {
return "sigil";
}
/*
* synthesize an ivy descriptor for a Sigil dependency. called after Sigil
* resolution, so descriptor already contains resolved version.
*/
public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
ResolvedResource ref = null;
ModuleRevisionId id = dd.getDependencyRevisionId();
if (!id.getOrganisation().equals(ORG_SIGIL)) {
return null;
}
ISigilBundle bundle = resolve(id);
if (bundle == null) {
return null;
}
String symbolicName = id.getName();
String version = bundle.getVersion().toString();
Resource res = new SigilIvy(extractBCP ? bundle : null, symbolicName, version);
ref = new ResolvedResource(res, version);
Log.debug(format("findIvyFileRef: dd=%s => ref=%s", dd, ref));
return ref;
}
@Override
protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
String name = artifact.getName();
ModuleRevisionId id = artifact.getModuleRevisionId();
if (!id.getOrganisation().equals(ORG_SIGIL)) {
return null;
}
ISigilBundle bundle = resolve(id);
if (bundle == null) {
return null;
}
IBundleModelElement info = bundle.getBundleInfo();
URI uri = info.getUpdateLocation();
Resource res = null;
try {
URL url = (uri != null) ? uri.toURL() : bundle.getLocation().toFile().toURL();
if (name.contains("!")) {
String[] split = name.split("!");
url = new URL("jar:" + url + "!/" + split[1] + "." + artifact.getExt());
}
res = new URLResource(url);
} catch (MalformedURLException e) {
System.out.println("Oops! " + e);
}
String version = bundle.getVersion().toString();
ResolvedResource ref = new ResolvedResource(res, version);
Log.debug(format("findArtifactRef: artifact=%s, date=%s => ref=%s", artifact, date, ref));
return ref;
}
private ISigilBundle resolve(ModuleRevisionId id) {
String revision = id.getRevision();
String range = revision;
if (revision.indexOf(',') < 0) {
// SigilParser has already resolved the revision from the import
// version range.
// We now need to locate the same bundle to get its download URL.
// We must use an OSGi version range to specify the exact version,
// otherwise it will resolve to "specified version or above".
range = "[" + revision + "," + revision + "]";
}
RequiredBundle bundle = new RequiredBundle();
bundle.setSymbolicName(id.getName());
bundle.setVersions(VersionRange.parseVersionRange(range));
try {
IResolution resolution = resolveOrFail(bundle, false);
ISigilBundle[] bundles = resolution.getBundles().toArray(new ISigilBundle[0]);
if (bundles.length == 1) {
return bundles[0];
}
} catch (ResolutionException e) {
return null;
}
return null;
}
/*
* Implement BasicResolver abstract methods
*/
@Override
protected long get(Resource res, File dest) throws IOException {
FileUtil.copy(res.openStream(), dest, null);
long len = res.getContentLength();
Log.debug(format("get(%s, %s) = %d", res, dest, len));
return len;
}
@Override
public Resource getResource(String source) throws IOException {
source = encode(source);
Resource res = resourcesCache.get(source);
if (res == null) {
res = new URLResource(new URL(source));
resourcesCache.put(source, res);
}
Log.debug(format("SIGIL: getResource(%s) = %d", source, res));
return res;
}
private static String encode(String source) {
return source.trim().replaceAll(" ", "%20");
}
@SuppressWarnings("unchecked")
@Override
protected Collection findNames(Map tokenValues, String token) {
throw new Error("SigilResolver: findNames not supported.");
}
public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
throw new Error("SigilResolver: publish not supported.");
}
public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
// stop publish attempts being silently ignored.
throw new Error("SigilResolver: publish not supported.");
}
/*
* Synthesize a virtual ivy file for a Sigil dependency.
*/
static class SigilIvy implements Resource {
private StringBuilder content = new StringBuilder();
private String name;
private boolean exists = true;
private SigilIvy() {
}
public SigilIvy(ISigilBundle bundle, String module, String rev) {
this.name = "sigil!" + module + "#" + rev;
String org = ORG_SIGIL;
// FIXME: make status and pub configurable
String status = "release";
String pub = "20080912162859";
content.append("<ivy-module version=\"1.0\">\n");
content.append(format(
"<info organisation=\"%s\" module=\"%s\" revision=\"%s\" status=\"%s\" publication=\"%s\"/>\n",
org, module, rev, status, pub));
String bcp = readBundleClassPath(bundle);
if (bcp != null) {
content.append("<publications>\n");
for (String j : bcp.split(",\\s*")) {
if (j.equals(".")) {
content.append("<artifact/>\n");
} else if (!j.endsWith("component-activator.jar")) {
if (j.endsWith(".jar"))
j = j.substring(0, j.length() - 4);
content.append(format("<artifact name=\"%s!%s\"/>\n", module, j));
}
}
content.append("</publications>\n");
}
// TODO: add dependencies?
// <dependencies>
// <dependency org="org.apache" name="log4j" rev="1.2.12"
// revConstraint="[1.2,1.3)"/>
// </dependencies>
content.append("</ivy-module>\n");
}
private String readBundleClassPath(ISigilBundle bundle) {
if (bundle == null)
return null;
URI uri = bundle.getBundleInfo().getUpdateLocation();
InputStream is = null;
try {
URL url = (uri != null) ? uri.toURL() : bundle.getLocation().toFile().toURL();
is = url.openStream();
JarInputStream js = new JarInputStream(is, false);
Manifest m = js.getManifest();
if (m != null)
return (String) m.getMainAttributes().getValue("Bundle-ClassPath");
} catch (IOException e) {
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e2) {
}
}
}
return null;
}
public String toString() {
return "SigilIvy[" + name + "]";
}
// clone is used to read checksum files
// so clone(getName() + ".sha1").exists() should be false
public Resource clone(String cloneName) {
Log.debug("SigilIvy: clone: " + cloneName);
SigilIvy clone = new SigilIvy();
clone.name = cloneName;
clone.exists = false;
return clone;
}
public boolean exists() {
return exists;
}
public long getContentLength() {
return content.length();
}
public long getLastModified() {
// TODO Auto-generated method stub
Log.debug("NOT IMPLEMENTED: getLastModified");
return 0;
}
public String getName() {
return name;
}
public boolean isLocal() {
return false;
}
@SuppressWarnings("deprecation")
public InputStream openStream() throws IOException {
return new java.io.StringBufferInputStream(content.toString());
}
}
}