blob: 596aabacac65e89827b70c989c40e6a5c8f73fb4 [file] [log] [blame]
Richard S. Hall85bafab2009-07-13 13:25:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20package org.cauldron.bld.ivy;
21
22import static java.lang.String.format;
23
24import java.io.File;
25import java.io.IOException;
26import java.io.InputStream;
27import java.net.MalformedURLException;
28import java.net.URI;
29import java.net.URL;
30import java.util.Collection;
31import java.util.Date;
32import java.util.HashMap;
33import java.util.Map;
34import java.util.Properties;
35import java.util.jar.JarInputStream;
36import java.util.jar.Manifest;
37
38import org.apache.ivy.core.module.descriptor.Artifact;
39import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
40import org.apache.ivy.core.module.id.ModuleRevisionId;
41import org.apache.ivy.core.resolve.ResolveData;
42import org.apache.ivy.plugins.repository.Resource;
43import org.apache.ivy.plugins.repository.url.URLResource;
44import org.apache.ivy.plugins.resolver.BasicResolver;
45import org.apache.ivy.plugins.resolver.util.ResolvedResource;
46import org.apache.ivy.util.FileUtil;
47import org.cauldron.bld.config.BldFactory;
48import org.cauldron.bld.core.internal.model.osgi.RequiredBundle;
49import org.cauldron.sigil.model.IModelElement;
50import org.cauldron.sigil.model.common.VersionRange;
51import org.cauldron.sigil.model.eclipse.ISigilBundle;
52import org.cauldron.sigil.model.osgi.IBundleModelElement;
53import org.cauldron.sigil.repository.IResolution;
54import org.cauldron.sigil.repository.ResolutionException;
55
56/**
57 * This resolver is able to work with Sigil repositories.
58 * It does not allow publishing.
59 */
60public class SigilResolver extends BasicResolver implements IBldResolver {
61 /** the sigil-injected dependency organisation name */
62 static final String ORG_SIGIL = "sigil";
63
64 private IBldResolver resolver;
65 private String config;
66 private boolean extractBCP = false;
67 private Map<String, Resource> resourcesCache = new HashMap<String, Resource>();
68
69 /**
70 * no-args constructor required by Ivy.
71 *
72 * XXX: It doesn't currently seem to matter that Ivy instantiates this many times,
73 * since SigilParser caches it, and the ProjectRepositoryProvider static cache
74 * prevents it being re-loaded unnecessarily.
75 *
76 * If this should become a problem, then we will need to delegate to a singleton instance,
77 * as we have done in SigilParser.
78 */
79 public SigilResolver() {
80 }
81
82 public void setConfig(String config) {
83 this.config = config;
84 }
85
86 /**
87 * if true, resolver extracts any jars embedded in Bundle-ClassPath.
88 */
89 public void setExtractBCP(String extract) {
90 this.extractBCP = Boolean.parseBoolean(extract);
91 }
92
93 private IBldResolver getResolver() {
94 if (resolver == null) {
95 if (config == null) {
96 throw new Error("SigilResolver: not configured. Specify config=\"PATH\" in ivysettings.xml.");
97 }
98 try {
99 URI uri;
100 File file = new File(config);
101
102 if (file.isAbsolute()) {
103 uri = file.toURI();
104 } else {
105 URI cwd = new File(".").toURI();
106 uri = cwd.resolve(config);
107 }
108
109 Map<String, Properties> repositories = BldFactory.getConfig(uri).getRepositoryConfig();
110 resolver = new BldResolver(repositories);
111 } catch (IOException e) {
112 throw new Error("SigilResolver: failed to configure: " + e);
113 }
114 }
115 return resolver;
116 }
117
118 public IResolution resolveOrFail(IModelElement element, boolean transitive) throws ResolutionException {
119 return getResolver().resolveOrFail(element, transitive);
120 }
121
122 public IResolution resolve(IModelElement element, boolean transitive) {
123 return getResolver().resolve(element, transitive);
124 }
125
126 public String getTypeName() {
127 return "sigil";
128 }
129
130 /*
131 * synthesize an ivy descriptor for a Sigil dependency. called after Sigil
132 * resolution, so descriptor already contains resolved version.
133 */
134 public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
135 ResolvedResource ref = null;
136
137 ModuleRevisionId id = dd.getDependencyRevisionId();
138
139 if (!id.getOrganisation().equals(ORG_SIGIL)) {
140 return null;
141 }
142
143 ISigilBundle bundle = resolve(id);
144 if (bundle == null) {
145 return null;
146 }
147
148 String symbolicName = id.getName();
149 String version = bundle.getVersion().toString();
150
151 Resource res = new SigilIvy(extractBCP ? bundle : null, symbolicName, version);
152 ref = new ResolvedResource(res, version);
153
154 Log.debug(format("findIvyFileRef: dd=%s => ref=%s", dd, ref));
155 return ref;
156 }
157
158 @Override
159 protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
160 String name = artifact.getName();
161 ModuleRevisionId id = artifact.getModuleRevisionId();
162
163 if (!id.getOrganisation().equals(ORG_SIGIL)) {
164 return null;
165 }
166
167 ISigilBundle bundle = resolve(id);
168 if (bundle == null) {
169 return null;
170 }
171
172 IBundleModelElement info = bundle.getBundleInfo();
173 URI uri = info.getUpdateLocation();
174 Resource res = null;
175
176 try {
177 URL url = (uri != null) ? uri.toURL() : bundle.getLocation().toFile().toURL();
178 if (name.contains("!")) {
179 String[] split = name.split("!");
180 url = new URL("jar:" + url + "!/" + split[1] + "." + artifact.getExt());
181 }
182 res = new URLResource(url);
183 } catch (MalformedURLException e) {
184 System.out.println("Oops! " + e);
185 }
186
187 String version = bundle.getVersion().toString();
188 ResolvedResource ref = new ResolvedResource(res, version);
189
190 Log.debug(format("findArtifactRef: artifact=%s, date=%s => ref=%s", artifact, date, ref));
191 return ref;
192 }
193
194 private ISigilBundle resolve(ModuleRevisionId id) {
195 String revision = id.getRevision();
196 String range = revision;
197
198 if (revision.indexOf(',') < 0) {
199 // SigilParser has already resolved the revision from the import
200 // version range.
201 // We now need to locate the same bundle to get its download URL.
202 // We must use an OSGi version range to specify the exact version,
203 // otherwise it will resolve to "specified version or above".
204 range = "[" + revision + "," + revision + "]";
205 }
206
207 RequiredBundle bundle = new RequiredBundle();
208 bundle.setSymbolicName(id.getName());
209 bundle.setVersions(VersionRange.parseVersionRange(range));
210
211 try {
212 IResolution resolution = resolveOrFail(bundle, false);
213 ISigilBundle[] bundles = resolution.getBundles().toArray(new ISigilBundle[0]);
214 if (bundles.length == 1) {
215 return bundles[0];
216 }
217 } catch (ResolutionException e) {
218 return null;
219 }
220 return null;
221 }
222
223 /*
224 * Implement BasicResolver abstract methods
225 */
226
227 @Override
228 protected long get(Resource res, File dest) throws IOException {
229 FileUtil.copy(res.openStream(), dest, null);
230 long len = res.getContentLength();
231 Log.debug(format("get(%s, %s) = %d", res, dest, len));
232 return len;
233 }
234
235
236 @Override
237 public Resource getResource(String source) throws IOException {
238 source = encode(source);
239 Resource res = resourcesCache.get(source);
240 if (res == null) {
241 res = new URLResource(new URL(source));
242 resourcesCache.put(source, res);
243 }
244 Log.debug(format("SIGIL: getResource(%s) = %d", source, res));
245 return res;
246 }
247
248 private static String encode(String source) {
249 return source.trim().replaceAll(" ", "%20");
250 }
251
252 @SuppressWarnings("unchecked")
253 @Override
254 protected Collection findNames(Map tokenValues, String token) {
255 throw new Error("SigilResolver: findNames not supported.");
256 }
257
258 public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
259 throw new Error("SigilResolver: publish not supported.");
260 }
261
262 public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
263 // stop publish attempts being silently ignored.
264 throw new Error("SigilResolver: publish not supported.");
265 }
266
267 /*
268 * Synthesize a virtual ivy file for a Sigil dependency.
269 */
270 static class SigilIvy implements Resource {
271 private StringBuilder content = new StringBuilder();
272 private String name;
273 private boolean exists = true;
274
275 private SigilIvy() {
276 }
277
278 public SigilIvy(ISigilBundle bundle, String module, String rev) {
279 this.name = "sigil!" + module + "#" + rev;
280
281 String org = ORG_SIGIL;
282 // FIXME: make status and pub configurable
283 String status = "release";
284 String pub = "20080912162859";
285
286 content.append("<ivy-module version=\"1.0\">\n");
287
288 content.append(format(
289 "<info organisation=\"%s\" module=\"%s\" revision=\"%s\" status=\"%s\" publication=\"%s\"/>\n",
290 org, module, rev, status, pub));
291
292 String bcp = readBundleClassPath(bundle);
293 if (bcp != null) {
294 content.append("<publications>\n");
295 for (String j : bcp.split(",\\s*")) {
296 if (j.equals(".")) {
297 content.append("<artifact/>\n");
298 } else if (!j.endsWith("component-activator.jar")) {
299 if (j.endsWith(".jar"))
300 j = j.substring(0, j.length() - 4);
301 content.append(format("<artifact name=\"%s!%s\"/>\n", module, j));
302 }
303 }
304 content.append("</publications>\n");
305 }
306
307 // TODO: add dependencies?
308 // <dependencies>
309 // <dependency org="org.apache" name="log4j" rev="1.2.12"
310 // revConstraint="[1.2,1.3)"/>
311 // </dependencies>
312
313 content.append("</ivy-module>\n");
314 }
315
316 private String readBundleClassPath(ISigilBundle bundle) {
317 if (bundle == null)
318 return null;
319
320 URI uri = bundle.getBundleInfo().getUpdateLocation();
321 InputStream is = null;
322 try {
323 URL url = (uri != null) ? uri.toURL() : bundle.getLocation().toFile().toURL();
324 is = url.openStream();
325 JarInputStream js = new JarInputStream(is, false);
326 Manifest m = js.getManifest();
327 if (m != null)
328 return (String) m.getMainAttributes().getValue("Bundle-ClassPath");
329 } catch (IOException e) {
330 } finally {
331 if (is != null) {
332 try {
333 is.close();
334 } catch (IOException e2) {
335 }
336 }
337 }
338
339 return null;
340 }
341
342 public String toString() {
343 return "SigilIvy[" + name + "]";
344 }
345
346 // clone is used to read checksum files
347 // so clone(getName() + ".sha1").exists() should be false
348 public Resource clone(String cloneName) {
349 Log.debug("SigilIvy: clone: " + cloneName);
350 SigilIvy clone = new SigilIvy();
351 clone.name = cloneName;
352 clone.exists = false;
353 return clone;
354 }
355
356 public boolean exists() {
357 return exists;
358 }
359
360 public long getContentLength() {
361 return content.length();
362 }
363
364 public long getLastModified() {
365 // TODO Auto-generated method stub
366 Log.debug("NOT IMPLEMENTED: getLastModified");
367 return 0;
368 }
369
370 public String getName() {
371 return name;
372 }
373
374 public boolean isLocal() {
375 return false;
376 }
377
378 @SuppressWarnings("deprecation")
379 public InputStream openStream() throws IOException {
380 return new java.io.StringBufferInputStream(content.toString());
381 }
382 }
383}