blob: 1ab3772e05cbb95cabba58ec7f302340ce0dc36a [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 java.io.File;
23import java.io.IOException;
24import java.io.InputStream;
25import java.net.URI;
26import java.net.URL;
27import java.text.ParseException;
28import java.util.Arrays;
29import java.util.HashMap;
30import java.util.Map;
31import java.util.regex.Pattern;
32
33import org.apache.ivy.Ivy;
34import org.apache.ivy.core.IvyContext;
35import org.apache.ivy.core.module.descriptor.Artifact;
36import org.apache.ivy.core.module.descriptor.Configuration;
37import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
38import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
39import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
40import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
41import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
42import org.apache.ivy.core.module.id.ModuleRevisionId;
43import org.apache.ivy.core.settings.IvySettings;
44import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
45import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
46import org.apache.ivy.plugins.parser.ParserSettings;
47import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
48import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
49import org.apache.ivy.plugins.repository.Resource;
50import org.apache.ivy.plugins.repository.file.FileResource;
51import org.apache.ivy.plugins.repository.url.URLResource;
52import org.apache.ivy.plugins.resolver.DependencyResolver;
53import org.cauldron.bld.config.BldFactory;
54import org.cauldron.bld.config.IBldProject;
55import org.cauldron.sigil.model.IModelElement;
56import org.cauldron.sigil.model.common.VersionRange;
57import org.cauldron.sigil.model.eclipse.ISigilBundle;
58import org.cauldron.sigil.model.osgi.IBundleModelElement;
59import org.cauldron.sigil.model.osgi.IPackageImport;
60import org.cauldron.sigil.model.osgi.IRequiredBundle;
61import org.cauldron.sigil.repository.IResolution;
62
63public class SigilParser implements ModuleDescriptorParser {
64
65 private static DelegateParser instance;
66
67 // used by ProjectRepository
68 static DelegateParser instance() {
69 if (instance == null)
70 throw new IllegalStateException("SigilParser is not instantiated.");
71 return instance;
72 }
73
74 public SigilParser() {
75 if (instance == null) {
76 instance = new DelegateParser();
77 }
78 }
79
80 /**
81 * In IvyDE, IvyContext is not available, so we can't find the SigilResolver.
82 * This allows us to construct one.
83 * @deprecated temporary to support IvyDE
84 */
85 public void setConfig(String config) {
86 instance.config = config;
87 }
88
89 /**
90 * sets delegate parser.
91 * If not set, we delegate to the default Ivy parser.
92 * @param type name returned by desired parser's getType() method.
93 */
94 public void setDelegateType(String type) {
95 for (ModuleDescriptorParser parser : ModuleDescriptorParserRegistry.getInstance().getParsers()) {
96 if (parser.getType().equals(type)) {
97 instance.delegate = parser;
98 break;
99 }
100 }
101
102 if (instance.delegate == null) {
103 throw new IllegalArgumentException("Can't find parser delegateType=" + type);
104 }
105 }
106
107 /**
108 * sets default file name the delegate parser accepts.
109 * If not set, defaults to "ivy.xml".
110 * @param name
111 */
112 public void setDelegateFile(String name) {
113 instance.ivyFile = name;
114 }
115
116 /**
117 * sets the dependencies to keep from the delegate parser.
118 * If not set, all existing dependencies are dropped.
119 * @param regex pattern matching dependency names to keep.
120 */
121 public void setKeepDependencies(String regex) {
122 instance.keepDependencyPattern = Pattern.compile(regex);
123 }
124
125 /**
126 * reduce level of info logging.
127 * @param quiet
128 */
129 public void setQuiet(String quiet) {
130 instance.quiet = Boolean.parseBoolean(quiet);
131 }
132
133 /**
134 * sets the SigilResolver we use.
135 * If not set, we use the first SigilResolver we find.
136 * @param name
137 */
138 public void setResolver(String name) {
139 instance.resolverName = name;
140 }
141
142 /**
143 * adds override element: <override name="X" pattern="Y" replace="Z"/>. Overrides
144 * Ivy variables using a pattern substitution on the resource directory path.
145 *
146 * @deprecated
147 * This is only needed when a delegate parser expects Ant variables to be set
148 * during the ivy:buildlist task (or the ProjectRepository initialisation),
149 * which they are not. The delegate parser should really be fixed, as otherwise
150 * the ivy:buildlist task won't work without this workaround.
151 */
152 // e.g. <override name="ant.project.name" pattern=".*/([^/]+)/([^/]+)$" replace="$1-$2"/>
153 public void addConfiguredOverride(Map<String, String> var) {
154 final String name = var.get("name");
155 final String regex = var.get("pattern");
156 final String replace = var.get("replace");
157
158 if (name == null || regex == null || replace == null)
159 throw new IllegalArgumentException("override must contain name, pattern and replace attributes.");
160
161 instance.varRegex.put(name, Pattern.compile(regex));
162 instance.varReplace.put(name, replace);
163 }
164
165 // implement ModuleDescriptorParser interface by delegation to singleton instance.
166
167 public boolean accept(Resource res) {
168 return instance.accept(res);
169 }
170
171 public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) {
172 return instance.getMetadataArtifact(mrid, res);
173 }
174
175 public String getType() {
176 return instance.getType();
177 }
178
179 public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, boolean validate)
180 throws ParseException, IOException {
181 return instance.parseDescriptor(settings, xmlURL, validate);
182 }
183
184 public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, Resource res, boolean validate)
185 throws ParseException, IOException {
186 return instance.parseDescriptor(settings, xmlURL, res, validate);
187 }
188
189 public void toIvyFile(InputStream source, Resource res, File destFile, ModuleDescriptor md) throws ParseException,
190 IOException {
191 instance.toIvyFile(source, res, destFile, md);
192 }
193
194 /**
195 * delegating parser.
196 * (optionally) removes original dependencies and augments with sigil-determined dependencies.
197 */
198 static class DelegateParser extends XmlModuleDescriptorParser {
199
200 private static final String SIGIL_BUILDLIST = IBldProject.PROJECT_FILE;
201 private static final String KEEP_ALL = ".*";
202
203 private IBldResolver resolver;
204 private ParserSettings defaultSettings;
205 private Map<String, DefaultModuleDescriptor> rawCache = new HashMap<String, DefaultModuleDescriptor>();
206 private Map<String, DefaultModuleDescriptor> augmentedCache = new HashMap<String, DefaultModuleDescriptor>();
207
208 private HashMap<String, String> varReplace = new HashMap<String, String>();
209 private HashMap<String, Pattern> varRegex = new HashMap<String, Pattern>();
210
211 private ModuleDescriptorParser delegate;
212 private String ivyFile = "ivy.xml";
213 private String resolverName;
214 private Pattern keepDependencyPattern;
215 private boolean quiet;
216 private String config;
217
218 @Override
219 public boolean accept(Resource res) {
220 boolean accept = (res instanceof SigilResolver.SigilIvy)
221 || res.getName().endsWith("/" + SIGIL_BUILDLIST)
222 || ((instance.getSigilURI(res) != null) &&
223 ((instance.delegate != null ? instance.delegate.accept(res) : false) || super.accept(res)));
224 if (accept)
225 Log.verbose("accepted: " + res);
226 return accept;
227 }
228
229 @Override
230 public void toIvyFile(InputStream source, Resource res, File destFile, ModuleDescriptor md)
231 throws ParseException, IOException {
232 Log.verbose("writing resource: " + res + " toIvyFile: " + destFile);
233 // source allows us to keep layout and comments,
234 // but this doesn't work if source is not ivy.xml.
235 // So just write file directly from descriptor.
236 try {
237 XmlModuleDescriptorWriter.write(md, destFile);
238 } finally {
239 if (source != null)
240 source.close();
241 }
242 }
243
244 @Override
245 public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, boolean validate)
246 throws ParseException, IOException {
247 return parseDescriptor(settings, xmlURL, new URLResource(xmlURL), validate);
248 }
249
250 @Override
251 public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, Resource res, boolean validate)
252 throws ParseException, IOException {
253 String cacheKey = xmlURL.toString() + settings.hashCode();
254 DefaultModuleDescriptor dmd = augmentedCache.get(cacheKey);
255
256 if (dmd == null) {
257 dmd = rawCache.get(cacheKey);
258 if (dmd == null) {
259 dmd = delegateParse(settings, xmlURL, res, validate);
260 }
261
262 if (!quiet)
263 Log.info("augmenting module=" + dmd.getModuleRevisionId().getName() +
264 " ant.project.name=" + settings.substitute("${ant.project.name}"));
265
266 addDependenciesToDescriptor(res, dmd);
267 augmentedCache.put(cacheKey, dmd);
268
269 Log.verbose("augmented dependencies: " + Arrays.asList(dmd.getDependencies()));
270 }
271
272 return dmd;
273 }
274
275 // used by ProjectRepository
276 ModuleDescriptor parseDescriptor(URL projectURL) throws ParseException, IOException {
277 if (defaultSettings == null) {
278 Ivy ivy = IvyContext.getContext().peekIvy();
279 if (ivy == null)
280 throw new IllegalStateException("can't get default settings - no ivy context.");
281 defaultSettings = ivy.getSettings();
282 }
283
284 URL ivyURL = new URL(projectURL, ivyFile);
285 String cacheKey = ivyURL.toString() + defaultSettings.hashCode();
286 DefaultModuleDescriptor dmd = rawCache.get(cacheKey);
287
288 if (dmd == null) {
289 URLResource res = new URLResource(ivyURL);
290 // Note: this doesn't contain the augmented dependencies, which is OK,
291 // since the ProjectRepository only needs the id and status.
292 dmd = delegateParse(defaultSettings, ivyURL, res, false);
293 rawCache.put(cacheKey, dmd);
294 }
295
296 return dmd;
297 }
298
299 private DefaultModuleDescriptor delegateParse(ParserSettings settings, URL xmlURL, Resource res,
300 boolean validate) throws ParseException, IOException {
301 String resName = res.getName();
302
303 if (resName.endsWith("/" + SIGIL_BUILDLIST)) {
304 String name = resName.substring(0, resName.length() - SIGIL_BUILDLIST.length());
305 res = res.clone(name + ivyFile);
306 xmlURL = new URL(xmlURL, ivyFile);
307 }
308
309 if (settings instanceof IvySettings) {
310 IvySettings ivySettings = (IvySettings) settings;
311 String dir = new File(res.getName()).getParent();
312
313 for (String name : varRegex.keySet()) {
314 Pattern regex = varRegex.get(name);
315 String replace = varReplace.get(name);
316
317 String value = regex.matcher(dir).replaceAll(replace);
318
319 Log.debug("overriding variable " + name + "=" + value);
320 ivySettings.setVariable(name, value);
321 }
322 }
323
324 ModuleDescriptor md = null;
325 if (delegate == null || !delegate.accept(res)) {
326 md = super.parseDescriptor(settings, xmlURL, res, validate);
327 } else {
328 md = delegate.parseDescriptor(settings, xmlURL, res, validate);
329 }
330
331 return mungeDescriptor(md, res);
332 }
333
334 /**
335 * clones descriptor and removes dependencies, as descriptor MUST have
336 * 'this' as the parser given to its constructor.
337 * Only dependency names matched by keepDependencyPattern are kept,
338 * as we're going to add our own dependencies later.
339 */
340 private DefaultModuleDescriptor mungeDescriptor(ModuleDescriptor md, Resource res) {
341
342 if ((md instanceof DefaultModuleDescriptor) &&
343 (md.getParser() == this) &&
344 (KEEP_ALL.equals(keepDependencyPattern))) {
345 return (DefaultModuleDescriptor) md;
346 }
347
348 DefaultModuleDescriptor dmd = new DefaultModuleDescriptor(this, res);
349
350 dmd.setModuleRevisionId(md.getModuleRevisionId());
351 dmd.setPublicationDate(md.getPublicationDate());
352
353 for (Configuration c : md.getConfigurations()) {
354 String conf = c.getName();
355
356 dmd.addConfiguration(c);
357
358 for (Artifact a : md.getArtifacts(conf)) {
359 dmd.addArtifact(conf, a);
360 }
361 }
362
363 if (keepDependencyPattern != null) {
364 for (DependencyDescriptor dependency : md.getDependencies()) {
365 String name = dependency.getDependencyId().getName();
366 if (keepDependencyPattern.matcher(name).matches()) {
367 dmd.addDependency(dependency);
368 }
369 }
370 }
371
372 return dmd;
373 }
374
375 /*
376 * find URI to Sigil project file. This assumes that it is in the same
377 * directory as the ivy file.
378 */
379 private URI getSigilURI(Resource res) {
380 URI uri = null;
381
382 if (res instanceof URLResource) {
383 URL url = ((URLResource) res).getURL();
384 uri = URI.create(url.toString()).resolve(IBldProject.PROJECT_FILE);
385 try {
386 InputStream stream = uri.toURL().openStream(); // check file
387 // exists
388 stream.close();
389 } catch (IOException e) {
390 uri = null;
391 }
392 } else if (res instanceof FileResource) {
393 uri = ((FileResource) res).getFile().toURI().resolve(IBldProject.PROJECT_FILE);
394 if (!(new File(uri).exists()))
395 uri = null;
396 }
397
398 return uri;
399 }
400
401 /*
402 * add sigil dependencies to ModuleDescriptor.
403 */
404 private void addDependenciesToDescriptor(Resource res, DefaultModuleDescriptor md) throws IOException {
405 // FIXME: transitive should be configurable
406 final boolean transitive = true; // ivy default is true
407 final boolean changing = false;
408 final boolean force = false;
409
410 URI uri = getSigilURI(res);
411 if (uri == null)
412 return;
413
414 IBldProject project;
415
416 project = BldFactory.getProject(uri);
417
418 IBundleModelElement requirements = project.getDependencies();
419 Log.verbose("requirements: " + Arrays.asList(requirements.children()));
420
421 // preserve version range for Require-Bundle
422 // XXX: synthesise bundle version range corresponding to package version ranges?
423 HashMap<String, VersionRange> versions = new HashMap<String, VersionRange>();
424 for (IModelElement child : requirements.children()) {
425 if (child instanceof IRequiredBundle) {
426 IRequiredBundle bundle = (IRequiredBundle) child;
427 versions.put(bundle.getSymbolicName(), bundle.getVersions());
428 }
429 }
430
431 IBldResolver resolver = findResolver();
432 if (resolver == null) {
433 // this can happen in IvyDE, but it retries and is OK next time.
434 Log.warn("failed to find resolver - IvyContext not yet available.");
435 return;
436 }
437
438 IResolution resolution = resolver.resolve(requirements, false);
439 Log.verbose("resolution: " + resolution.getBundles());
440
441 ModuleRevisionId masterMrid = md.getModuleRevisionId();
442 DefaultDependencyDescriptor dd;
443 ModuleRevisionId mrid;
444
445 for (ISigilBundle bundle : resolution.getBundles()) {
446 IBundleModelElement info = bundle.getBundleInfo();
447 String name = info.getSymbolicName();
448
449 if (name == null) {
450 // e.g. SystemProvider with framework=null
451 continue;
452 }
453
454 if (bundle instanceof ProjectRepository.ProjectBundle) {
455 ProjectRepository.ProjectBundle pb = (ProjectRepository.ProjectBundle) bundle;
456 String org = pb.getOrg();
457 if (org == null)
458 org = masterMrid.getOrganisation();
459 String id = pb.getId();
460 String module = pb.getModule();
461 String rev = pb.getRevision();
462
463 mrid = ModuleRevisionId.newInstance(org, module, rev);
464 dd = new SigilDependencyDescriptor(md, mrid, force, changing, transitive);
465
466 if (!id.equals(module)) { // non-default artifact
467 dd.addDependencyArtifact(module,
468 new DefaultDependencyArtifactDescriptor(dd, id, "jar", "jar", null, null));
469 }
470 } else {
471 VersionRange version = versions.get(name);
472 String rev = version != null ? version.toString() : info.getVersion().toString();
473 mrid = ModuleRevisionId.newInstance(SigilResolver.ORG_SIGIL, name, rev);
474 dd = new SigilDependencyDescriptor(md, mrid, force, changing, transitive);
475 }
476
477 int nDeps = 0;
478 boolean foundDefault = false;
479
480 // TODO: make dependency configurations configurable SIGIL-176
481 for (String conf : md.getConfigurationsNames()) {
482 if (conf.equals("default")) {
483 foundDefault = true;
484 } else if (md.getArtifacts(conf).length == 0) {
485 dd.addDependencyConfiguration(conf, conf + "(default)");
486 nDeps++;
487 }
488 }
489
490 if (nDeps > 0) {
491 if (foundDefault)
492 dd.addDependencyConfiguration("default", "default");
493 } else {
494 dd.addDependencyConfiguration("*", "*"); // all configurations
495 }
496
497 md.addDependency(dd);
498 }
499
500 boolean resolved = true;
501 for (IModelElement child : requirements.children()) {
502 ISigilBundle provider = resolution.getProvider(child);
503 if (provider == null) {
504 resolved = false;
505 // this is parse phase, so only log verbose message.
506 // error is produced during resolution phase.
507 Log.verbose("WARN: can't resolve: " + child);
508
509 String name;
510 String version;
511 if (child instanceof IRequiredBundle) {
512 IRequiredBundle rb = (IRequiredBundle) child;
513 name = rb.getSymbolicName();
514 version = rb.getVersions().toString();
515 } else {
516 IPackageImport pi = (IPackageImport) child;
517 name = "import!" + pi.getPackageName();
518 version = pi.getVersions().toString();
519 }
520
521 mrid = ModuleRevisionId.newInstance("!" + SigilResolver.ORG_SIGIL, name, version);
522 dd = new SigilDependencyDescriptor(md, mrid, force, changing, transitive);
523 dd.addDependencyConfiguration("*", "*"); // all
524 // configurations
525 md.addDependency(dd);
526 }
527 }
528
529 if (!resolved) {
530 // Ivy does not show warnings or errors logged from parse phase, until after resolution.
531 // but <buildlist> ant task doesn't do resolution, so errors would be silently ignored.
532 // Hence, we must use Message.info to ensure this failure is seen.
533 if (!quiet)
534 Log.info("WARN: resolution failed in: " + masterMrid.getName() + ". Use -v for details.");
535 }
536 }
537
538 /*
539 * find named resolver, or first resolver that implements IBldResolver.
540 */
541 private IBldResolver findResolver() {
542 if (resolver == null) {
543 Ivy ivy = IvyContext.getContext().peekIvy();
544 if (ivy != null) {
545 if (resolverName != null) {
546 DependencyResolver r = ivy.getSettings().getResolver(resolverName);
547 if (r == null) {
548 throw new Error("SigilParser: resolver \"" + resolverName + "\" not defined.");
549 } else if (r instanceof IBldResolver) {
550 resolver = (IBldResolver) r;
551 } else {
552 throw new Error("SigilParser: resolver \"" + resolverName + "\" is not a SigilResolver.");
553 }
554 } else {
555 for (Object r : ivy.getSettings().getResolvers()) {
556 if (r instanceof IBldResolver) {
557 resolver = (IBldResolver) r;
558 break;
559 }
560 }
561 }
562
563 if (resolver == null) {
564 throw new Error("SigilParser: can't find SigilResolver. Check ivysettings.xml.");
565 }
566 } else if (config != null) {
567 Log.warn("creating duplicate resolver to support IvyDE.");
568 resolver = new SigilResolver();
569 ((SigilResolver)resolver).setConfig(config);
570 }
571 }
572 return resolver;
573 }
574 }
575
576}
577
578/*
579 * this is only needed so that we can distinguish sigil-added dependencies from
580 * existing ones.
581 */
582class SigilDependencyDescriptor extends DefaultDependencyDescriptor {
583 public SigilDependencyDescriptor(DefaultModuleDescriptor md, ModuleRevisionId mrid, boolean force,
584 boolean changing, boolean transitive) {
585 super(md, mrid, force, changing, transitive);
586 }
587}