Add .tox/ to .gitignore
[odlparent.git] / karaf / karaf-maven-plugin / src / main / java / org / apache / karaf / tooling / VerifyMojo.java
1 /*
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 package org.apache.karaf.tooling;
20
21 import java.io.File;
22 import java.io.FileReader;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.Reader;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.InvocationHandler;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Proxy;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.EnumSet;
35 import java.util.Enumeration;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Hashtable;
39 import java.util.Iterator;
40 import java.util.LinkedHashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Properties;
44 import java.util.Set;
45 import java.util.TreeSet;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.ScheduledExecutorService;
48 import java.util.concurrent.atomic.AtomicLong;
49 import java.util.jar.Attributes;
50 import java.util.jar.Manifest;
51 import java.util.regex.Pattern;
52 import java.util.zip.ZipEntry;
53 import java.util.zip.ZipInputStream;
54
55 import aQute.bnd.osgi.Macro;
56 import aQute.bnd.osgi.Processor;
57 import org.apache.felix.resolver.Logger;
58 import org.apache.felix.resolver.ResolverImpl;
59 import org.apache.felix.utils.version.VersionRange;
60 import org.apache.felix.utils.version.VersionTable;
61 import org.apache.karaf.features.FeatureEvent;
62 import org.apache.karaf.features.FeaturesService;
63 import org.apache.karaf.features.internal.download.DownloadCallback;
64 import org.apache.karaf.features.internal.download.DownloadManager;
65 import org.apache.karaf.features.internal.download.Downloader;
66 import org.apache.karaf.features.internal.download.StreamProvider;
67 import org.apache.karaf.features.internal.model.Conditional;
68 import org.apache.karaf.features.internal.model.ConfigFile;
69 import org.apache.karaf.features.internal.model.Feature;
70 import org.apache.karaf.features.internal.model.Features;
71 import org.apache.karaf.features.internal.model.JaxbUtil;
72 import org.apache.karaf.features.internal.resolver.ResourceBuilder;
73 import org.apache.karaf.features.internal.resolver.ResourceImpl;
74 import org.apache.karaf.features.internal.resolver.ResourceUtils;
75 import org.apache.karaf.features.internal.service.Deployer;
76 import org.apache.karaf.features.internal.service.State;
77 import org.apache.karaf.features.internal.util.MapUtils;
78 import org.apache.karaf.features.internal.util.MultiException;
79 import org.apache.karaf.profile.assembly.CustomDownloadManager;
80 import org.apache.karaf.tooling.utils.MojoSupport;
81 import org.apache.karaf.util.config.PropertiesLoader;
82 import org.apache.maven.artifact.Artifact;
83 import org.apache.maven.plugin.MojoExecutionException;
84 import org.apache.maven.plugin.MojoFailureException;
85 import org.apache.maven.plugins.annotations.Component;
86 import org.apache.maven.plugins.annotations.Mojo;
87 import org.apache.maven.plugins.annotations.Parameter;
88 import org.apache.maven.plugins.annotations.ResolutionScope;
89 import org.apache.maven.project.MavenProject;
90 import org.apache.maven.settings.Settings;
91 import org.ops4j.pax.url.mvn.MavenResolver;
92 import org.ops4j.pax.url.mvn.MavenResolvers;
93 import org.osgi.framework.Bundle;
94 import org.osgi.framework.BundleException;
95 import org.osgi.framework.Constants;
96 import org.osgi.framework.InvalidSyntaxException;
97 import org.osgi.framework.Version;
98 import org.osgi.framework.namespace.IdentityNamespace;
99 import org.osgi.framework.startlevel.BundleStartLevel;
100 import org.osgi.framework.wiring.BundleCapability;
101 import org.osgi.framework.wiring.BundleRequirement;
102 import org.osgi.framework.wiring.BundleRevision;
103 import org.osgi.framework.wiring.BundleWiring;
104 import org.osgi.resource.Requirement;
105 import org.osgi.resource.Resource;
106 import org.osgi.resource.Wire;
107 import org.osgi.service.resolver.ResolutionException;
108
109 import static java.util.jar.JarFile.MANIFEST_NAME;
110
111 @Mojo(name = "verify", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true)
112 public class VerifyMojo extends MojoSupport {
113
114     @Parameter(property = "descriptors")
115     protected Set<String> descriptors;
116
117     @Parameter(property = "features")
118     protected Set<String> features;
119
120     @Parameter(property = "framework")
121     protected Set<String> framework;
122
123     @Parameter(property = "configuration")
124     protected String configuration;
125
126     @Parameter(property = "distribution", defaultValue = "org.apache.karaf:apache-karaf")
127     protected String distribution;
128
129     @Parameter(property = "javase")
130     protected String javase;
131
132     @Parameter(property = "dist-dir")
133     protected String distDir;
134
135     @Parameter(property = "additional-metadata")
136     protected File additionalMetadata;
137
138     @Parameter(property = "ignore-missing-conditions")
139     protected boolean ignoreMissingConditions;
140
141     @Parameter(property = "fail")
142     protected String fail = "end";
143
144     @Parameter(property = "verify-transitive")
145     protected boolean verifyTransitive = false;
146
147     @Parameter(defaultValue = "${project}", readonly = true)
148     protected MavenProject project;
149
150     protected MavenResolver resolver;
151
152     @Override
153     public void execute() throws MojoExecutionException, MojoFailureException {
154         Hashtable<String, String> config = new Hashtable<>();
155         StringBuilder remote = new StringBuilder();
156         for (Object obj : project.getRemoteProjectRepositories()) {
157             if (remote.length() > 0) {
158                 remote.append(",");
159             }
160             remote.append(invoke(obj, "getUrl"));
161             remote.append("@id=").append(invoke(obj, "getId"));
162             if (!((Boolean) invoke(getPolicy(obj, false), "isEnabled"))) {
163                 remote.append("@noreleases");
164             }
165             if ((Boolean) invoke(getPolicy(obj, true), "isEnabled")) {
166                 remote.append("@snapshots");
167             }
168         }
169         getLog().info("Using repositories: " + remote.toString());
170         config.put("maven.repositories", remote.toString());
171         config.put("maven.localRepository", localRepo.getBasedir());
172         config.put("maven.settings", mavenSession.getRequest().getUserSettingsFile().toString());
173         // TODO: add more configuration bits ?
174         resolver = MavenResolvers.createMavenResolver(config, "maven");
175         doExecute();
176     }
177
178     private Object invoke(Object object, String getter) throws MojoExecutionException {
179         try {
180             return object.getClass().getMethod(getter).invoke(object);
181         } catch (Exception e) {
182             throw new MojoExecutionException("Unable to build remote repository from " + object.toString(), e);
183         }
184     }
185
186     private Object getPolicy(Object object, boolean snapshots) throws MojoExecutionException {
187         return invoke(object, "getPolicy", new Class[] { Boolean.TYPE }, new Object[] { snapshots });
188     }
189
190     private Object invoke(Object object, String getter, Class[] types, Object[] params) throws MojoExecutionException {
191         try {
192             return object.getClass().getMethod(getter, types).invoke(object, params);
193         } catch (Exception e) {
194             throw new MojoExecutionException("Unable to build remote repository from " + object.toString(), e);
195         }
196     }
197
198     protected void doExecute() throws MojoExecutionException, MojoFailureException {
199         System.setProperty("karaf.home", "target/karaf");
200         System.setProperty("karaf.data", "target/karaf/data");
201
202         Hashtable<String, String> properties = new Hashtable<>();
203
204         if (additionalMetadata != null) {
205             try (Reader reader = new FileReader(additionalMetadata)) {
206                 Properties metadata = new Properties();
207                 metadata.load(reader);
208                 for (Enumeration<?> e = metadata.propertyNames(); e.hasMoreElements(); ) {
209                     Object key = e.nextElement();
210                     Object val = metadata.get(key);
211                     properties.put(key.toString(), val.toString());
212                 }
213             } catch (IOException e) {
214                 throw new MojoExecutionException("Unable to load additional metadata from " + additionalMetadata, e);
215             }
216         }
217
218         // TODO: allow using external configuration ?
219         ScheduledExecutorService executor = Executors.newScheduledThreadPool(8);
220         DownloadManager manager = new CustomDownloadManager(resolver, executor);
221         final Map<String, Features> repositories;
222         Map<String, List<Feature>> allFeatures = new HashMap<>();
223         try {
224             repositories = loadRepositories(manager, descriptors);
225             for (String repoUri : repositories.keySet()) {
226                 List<Feature> features = repositories.get(repoUri).getFeature();
227                 // Ack features to inline configuration files urls
228                 for (Feature feature : features) {
229                     for (org.apache.karaf.features.internal.model.Bundle bi : feature.getBundle()) {
230                         String loc = bi.getLocation();
231                         String nloc = null;
232                         if (loc.contains("file:")) {
233                             for (ConfigFile cfi : feature.getConfigfile()) {
234                                 if (cfi.getFinalname().substring(1)
235                                         .equals(loc.substring(loc.indexOf("file:") + "file:".length()))) {
236                                     nloc = cfi.getLocation();
237                                 }
238                             }
239                         }
240                         if (nloc != null) {
241                             Field field = bi.getClass().getDeclaredField("location");
242                             field.setAccessible(true);
243                             field.set(bi, loc.substring(0, loc.indexOf("file:")) + nloc);
244                         }
245                     }
246                 }
247                 allFeatures.put(repoUri, features);
248             }
249         } catch (Exception e) {
250             throw new MojoExecutionException("Unable to load features descriptors", e);
251         }
252
253         List<Feature> featuresToTest = new ArrayList<>();
254         if (verifyTransitive) {
255             for (List<Feature> features : allFeatures.values()) {
256                 featuresToTest.addAll(features);
257             }
258         } else {
259             for (String uri : descriptors) {
260                 featuresToTest.addAll(allFeatures.get(uri));
261             }
262         }
263         if (features != null && !features.isEmpty()) {
264             StringBuilder sb = new StringBuilder();
265             for (String feature : features) {
266                 if (sb.length() > 0) {
267                     sb.append("|");
268                 }
269                 String p = feature.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*");
270                 sb.append(p);
271                 if (!feature.contains("/")) {
272                     sb.append("/.*");
273                 }
274             }
275             Pattern pattern = Pattern.compile(sb.toString());
276             for (Iterator<Feature> iterator = featuresToTest.iterator(); iterator.hasNext();) {
277                 Feature feature = iterator.next();
278                 String id = feature.getName() + "/" + feature.getVersion();
279                 if (!pattern.matcher(id).matches()) {
280                     iterator.remove();
281                 }
282             }
283         }
284
285         for (String fmk : framework) {
286             properties.put("feature.framework." + fmk, fmk);
287         }
288         List<Exception> failures = new ArrayList<>();
289         for (Feature feature : featuresToTest) {
290             try {
291                 String id = feature.getName() + "/" + feature.getVersion();
292                 verifyResolution(new CustomDownloadManager(resolver, executor),
293                                  repositories, Collections.singleton(id), properties);
294                 getLog().info("Verification of feature " + id + " succeeded");
295             } catch (Exception e) {
296                 if (e.getCause() instanceof ResolutionException) {
297                     getLog().warn(e.getMessage());
298                 } else {
299                     getLog().warn(e);
300                 }
301                 failures.add(e);
302                 if ("first".equals(fail)) {
303                     throw e;
304                 }
305             }
306             for (Conditional cond : feature.getConditional()) {
307                 Set<String> ids = new LinkedHashSet<>();
308                 ids.add(feature.getId());
309                 ids.addAll(cond.getCondition());
310                 try {
311                     verifyResolution(manager, repositories, ids, properties);
312                     getLog().info("Verification of feature " + ids + " succeeded");
313                 } catch (Exception e) {
314                     if (ignoreMissingConditions && e.getCause() instanceof ResolutionException) {
315                         boolean ignore = true;
316                         Collection<Requirement> requirements = ((ResolutionException) e.getCause()).getUnresolvedRequirements();
317                         for (Requirement req : requirements) {
318                             ignore &= (IdentityNamespace.IDENTITY_NAMESPACE.equals(req.getNamespace())
319                                     && ResourceUtils.TYPE_FEATURE.equals(req.getAttributes().get("type"))
320                                     && cond.getCondition().contains(req.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE).toString()));
321                         }
322                         if (ignore) {
323                             getLog().warn("Feature resolution failed for " + ids
324                                     + "\nMessage: " + e.getCause().getMessage());
325                             continue;
326                         }
327                     }
328                     if (e.getCause() instanceof ResolutionException) {
329                         getLog().warn(e.getMessage());
330                     } else {
331                         getLog().warn(e);
332                     }
333                     failures.add(e);
334                     if ("first".equals(fail)) {
335                         throw e;
336                     }
337                 }
338             }
339         }
340         if ("end".equals(fail) && !failures.isEmpty()) {
341             throw new MojoExecutionException("Verification failures", new MultiException("Verification failures", failures));
342         }
343     }
344
345     private void verifyResolution(DownloadManager manager, final Map<String, Features> repositories, Set<String> features, Hashtable<String, String> properties) throws MojoExecutionException {
346         try {
347             Bundle systemBundle = getSystemBundle(getMetadata(properties, "metadata#"));
348             DummyDeployCallback callback = new DummyDeployCallback(systemBundle, repositories.values());
349             Deployer deployer = new Deployer(manager, new ResolverImpl(new MavenResolverLog()), callback);
350
351
352             // Install framework
353             Deployer.DeploymentRequest request = createDeploymentRequest();
354
355             for (String fmwk : framework) {
356                 MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, fmwk);
357             }
358             try {
359                 deployer.deploy(callback.getDeploymentState(), request);
360             } catch (Exception e) {
361                 throw new MojoExecutionException("Unable to resolve framework features", e);
362             }
363
364
365             /*
366             boolean resolveOptionalImports = getResolveOptionalImports(properties);
367
368             DeploymentBuilder builder = new DeploymentBuilder(
369                     manager,
370                     null,
371                     repositories.values(),
372                     -1 // Disable url handlers
373             );
374             Map<String, Resource> downloadedResources = builder.download(
375                     getPrefixedProperties(properties, "feature."),
376                     getPrefixedProperties(properties, "bundle."),
377                     getPrefixedProperties(properties, "fab."),
378                     getPrefixedProperties(properties, "req."),
379                     getPrefixedProperties(properties, "override."),
380                     getPrefixedProperties(properties, "optional."),
381                     getMetadata(properties, "metadata#")
382             );
383
384             for (String uri : getPrefixedProperties(properties, "resources.")) {
385                 builder.addResourceRepository(new MetadataRepository(new HttpMetadataProvider(uri)));
386             }
387             */
388
389
390             // Install features
391             for (String feature : features) {
392                 MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, feature);
393             }
394             try {
395                 Set<String> prereqs = new HashSet<>();
396                 while (true) {
397                     try {
398                         deployer.deploy(callback.getDeploymentState(), request);
399                         break;
400                     } catch (Deployer.PartialDeploymentException e) {
401                         if (!prereqs.containsAll(e.getMissing())) {
402                             prereqs.addAll(e.getMissing());
403                         } else {
404                             throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing());
405                         }
406                     }
407                 }
408                 // TODO: find unused resources ?
409             } catch (Exception e) {
410                 throw new MojoExecutionException("Feature resolution failed for " + features
411                         + "\nMessage: " + e.getMessage()
412                         + "\nRepositories: " + toString(new TreeSet<>(repositories.keySet()))
413                         + "\nResources: " + toString(new TreeSet<>(manager.getProviders().keySet())), e);
414             }
415
416
417         } catch (MojoExecutionException e) {
418             throw e;
419         } catch (Exception e) {
420             throw new MojoExecutionException("Error verifying feature " + features + "\nMessage: " + e.getMessage(), e);
421         }
422     }
423
424     private Deployer.DeploymentRequest createDeploymentRequest() {
425         Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
426         request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE;
427         request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE;
428         request.serviceRequirements = FeaturesService.SERVICE_REQUIREMENTS_DEFAULT;
429         request.overrides = new HashSet<>();
430         request.requirements = new HashMap<>();
431         request.stateChanges = new HashMap<>();
432         request.options = EnumSet.noneOf(FeaturesService.Option.class);
433         return request;
434     }
435
436     private String toString(Collection<String> collection) {
437         StringBuilder sb = new StringBuilder();
438         sb.append("{\n");
439         for (String s : collection) {
440             sb.append("\t").append(s).append("\n");
441         }
442         sb.append("}");
443         return sb.toString();
444     }
445
446     private Bundle getSystemBundle(Map<String, Map<VersionRange, Map<String, String>>> metadata) throws Exception {
447         URL configPropURL;
448         if (configuration != null) {
449             configPropURL = new URL(configuration);
450         } else {
451             Artifact karafDistro = project.getArtifactMap().get(distribution);
452             if (karafDistro == null) {
453                 throw new MojoFailureException("The karaf distribution " + distribution + " is not a dependency");
454             }
455             if ("kar".equals(karafDistro.getType()) && distDir == null) {
456                 distDir = "resources";
457             }
458             String dir = distDir;
459             if (dir == null) {
460                 dir = karafDistro.getArtifactId() + "-" + karafDistro.getBaseVersion();
461             }
462             configPropURL = new URL("jar:file:" + karafDistro.getFile() + "!/" + dir + "/etc/config.properties");
463         }
464         org.apache.felix.utils.properties.Properties configProps = PropertiesLoader.loadPropertiesFile(configPropURL, true);
465 //        copySystemProperties(configProps);
466         if (javase == null) {
467             configProps.put("java.specification.version", System.getProperty("java.specification.version"));
468         } else {
469             configProps.put("java.specification.version", javase);
470         }
471         configProps.substitute();
472
473         Attributes attributes = new Attributes();
474         attributes.putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
475         attributes.putValue(Constants.BUNDLE_SYMBOLICNAME, "system.bundle");
476         attributes.putValue(Constants.BUNDLE_VERSION, "0.0.0");
477
478         String exportPackages = configProps.getProperty("org.osgi.framework.system.packages");
479         if (configProps.containsKey("org.osgi.framework.system.packages.extra")) {
480             exportPackages += "," + configProps.getProperty("org.osgi.framework.system.packages.extra");
481         }
482         exportPackages = exportPackages.replaceAll(",\\s*,", ",");
483         attributes.putValue(Constants.EXPORT_PACKAGE, exportPackages);
484
485         String systemCaps = configProps.getProperty("org.osgi.framework.system.capabilities");
486         attributes.putValue(Constants.PROVIDE_CAPABILITY, systemCaps);
487
488         // TODO: support metadata overrides on system bundle
489 //        attributes = DeploymentBuilder.overrideAttributes(attributes, metadata);
490
491         final Hashtable<String, String> headers = new Hashtable<>();
492         for (Map.Entry attr : attributes.entrySet()) {
493             headers.put(attr.getKey().toString(), attr.getValue().toString());
494         }
495
496         final FakeBundleRevision resource = new FakeBundleRevision(headers, "system-bundle", 0l);
497         return resource.getBundle();
498     }
499
500
501     public static Map<String, Features> loadRepositories(DownloadManager manager, Set<String> uris) throws Exception {
502         final Map<String, Features> loaded = new HashMap<>();
503         final Downloader downloader = manager.createDownloader();
504         for (String repository : uris) {
505             downloader.download(repository, new DownloadCallback() {
506                 @Override
507                 public void downloaded(final StreamProvider provider) throws Exception {
508                     try (InputStream is = provider.open()) {
509                         Features featuresModel = JaxbUtil.unmarshal(provider.getUrl(), is, false);
510                         synchronized (loaded) {
511                             loaded.put(provider.getUrl(), featuresModel);
512                             for (String innerRepository : featuresModel.getRepository()) {
513                                 downloader.download(innerRepository, this);
514                             }
515                         }
516                     }
517                 }
518             });
519         }
520         downloader.await();
521         return loaded;
522     }
523
524     public static Set<String> getPrefixedProperties(Map<String, String> properties, String prefix) {
525         Set<String> result = new HashSet<>();
526         for (String key : properties.keySet()) {
527             if (key.startsWith(prefix)) {
528                 String url = properties.get(key);
529                 if (url == null || url.length() == 0) {
530                     url = key.substring(prefix.length());
531                 }
532                 if (!url.isEmpty()) {
533                     result.add(url);
534                 }
535             }
536         }
537         return result;
538     }
539
540     public static Map<String, Map<VersionRange, Map<String, String>>> getMetadata(Map<String, String> properties, String prefix) {
541         Map<String, Map<VersionRange, Map<String, String>>> result = new HashMap<>();
542         for (String key : properties.keySet()) {
543             if (key.startsWith(prefix)) {
544                 String val = properties.get(key);
545                 key = key.substring(prefix.length());
546                 String[] parts = key.split("#");
547                 if (parts.length == 3) {
548                     Map<VersionRange, Map<String, String>> ranges = result.get(parts[0]);
549                     if (ranges == null) {
550                         ranges = new HashMap<>();
551                         result.put(parts[0], ranges);
552                     }
553                     String version = parts[1];
554                     if (!version.startsWith("[") && !version.startsWith("(")) {
555                         Processor processor = new Processor();
556                         processor.setProperty("@", VersionTable.getVersion(version).toString());
557                         Macro macro = new Macro(processor);
558                         version = macro.process("${range;[==,=+)}");
559                     }
560                     VersionRange range = new VersionRange(version);
561                     Map<String, String> hdrs = ranges.get(range);
562                     if (hdrs == null) {
563                         hdrs = new HashMap<>();
564                         ranges.put(range, hdrs);
565                     }
566                     hdrs.put(parts[2], val);
567                 }
568             }
569         }
570         return result;
571     }
572
573     public static class FakeBundleRevision extends ResourceImpl implements BundleRevision, BundleStartLevel {
574
575         private final Bundle bundle;
576         private int startLevel;
577
578         public FakeBundleRevision(final Hashtable<String, String> headers, final String location, final long bundleId) throws BundleException {
579             ResourceBuilder.build(this, location, headers);
580             this.bundle = (Bundle) Proxy.newProxyInstance(
581                     getClass().getClassLoader(),
582                     new Class[] { Bundle.class },
583                     new InvocationHandler() {
584                         @Override
585                         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
586                             if (method.getName().equals("hashCode")) {
587                                 return FakeBundleRevision.this.hashCode();
588                             } else if (method.getName().equals("equals")) {
589                                 return proxy == args[0];
590                             } else if (method.getName().equals("toString")) {
591                                 return bundle.getSymbolicName() + "/" + bundle.getVersion();
592                             } else if (method.getName().equals("adapt")) {
593                                 if (args.length == 1 && args[0] == BundleRevision.class) {
594                                     return FakeBundleRevision.this;
595                                 } else if (args.length == 1 && args[0] == BundleStartLevel.class) {
596                                     return FakeBundleRevision.this;
597                                 }
598                             } else if (method.getName().equals("getHeaders")) {
599                                 return headers;
600                             } else if (method.getName().equals("getBundleId")) {
601                                 return bundleId;
602                             } else if (method.getName().equals("getLocation")) {
603                                 return location;
604                             } else if (method.getName().equals("getSymbolicName")) {
605                                 String name = headers.get(Constants.BUNDLE_SYMBOLICNAME);
606                                 int idx = name.indexOf(';');
607                                 if (idx > 0) {
608                                     name = name.substring(0, idx).trim();
609                                 }
610                                 return name;
611                             } else if (method.getName().equals("getVersion")) {
612                                 return new Version(headers.get(Constants.BUNDLE_VERSION));
613                             } else if (method.getName().equals("getState")) {
614                                 return Bundle.ACTIVE;
615                             } else if (method.getName().equals("getLastModified")) {
616                                 return 0l;
617                             }
618                             return null;
619                         }
620                     });
621         }
622
623         @Override
624         public int getStartLevel() {
625             return startLevel;
626         }
627
628         @Override
629         public void setStartLevel(int startLevel) {
630             this.startLevel = startLevel;
631         }
632
633         @Override
634         public boolean isPersistentlyStarted() {
635             return true;
636         }
637
638         @Override
639         public boolean isActivationPolicyUsed() {
640             return false;
641         }
642
643         @Override
644         public String getSymbolicName() {
645             return bundle.getSymbolicName();
646         }
647
648         @Override
649         public Version getVersion() {
650             return bundle.getVersion();
651         }
652
653         @Override
654         public List<BundleCapability> getDeclaredCapabilities(String namespace) {
655             throw new UnsupportedOperationException();
656         }
657
658         @Override
659         public List<BundleRequirement> getDeclaredRequirements(String namespace) {
660             throw new UnsupportedOperationException();
661         }
662
663         @Override
664         public int getTypes() {
665             throw new UnsupportedOperationException();
666         }
667
668         @Override
669         public BundleWiring getWiring() {
670             throw new UnsupportedOperationException();
671         }
672
673         @Override
674         public Bundle getBundle() {
675             return bundle;
676         }
677     }
678
679     public static class DummyDeployCallback implements Deployer.DeployCallback {
680
681         private final Bundle systemBundle;
682         private final Deployer.DeploymentState dstate;
683         private final AtomicLong nextBundleId = new AtomicLong(0);
684
685         public DummyDeployCallback(Bundle sysBundle, Collection<Features> repositories) throws Exception {
686             systemBundle = sysBundle;
687             dstate = new Deployer.DeploymentState();
688             dstate.bundles = new HashMap<>();
689             dstate.features = new HashMap<>();
690             dstate.bundlesPerRegion = new HashMap<>();
691             dstate.filtersPerRegion = new HashMap<>();
692             dstate.state = new State();
693
694             MapUtils.addToMapSet(dstate.bundlesPerRegion, FeaturesService.ROOT_REGION, 0l);
695             dstate.bundles.put(0l, systemBundle);
696             for (Features repo : repositories) {
697                 for (Feature f : repo.getFeature()) {
698                     dstate.features.put(f.getId(), f);
699                 }
700             }
701         }
702
703         public Deployer.DeploymentState getDeploymentState() {
704             return dstate;
705         }
706
707         @Override
708         public void print(String message, boolean verbose) {
709         }
710
711         @Override
712         public void saveState(State state) {
713             dstate.state.replace(state);
714         }
715
716         @Override
717         public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException {
718         }
719
720         @Override
721         public void installFeature(org.apache.karaf.features.Feature feature) throws IOException, InvalidSyntaxException {
722         }
723
724         @Override
725         public void callListeners(FeatureEvent featureEvent) {
726         }
727
728         @Override
729         public Bundle installBundle(String region, String uri, InputStream is) throws BundleException {
730             try {
731                 Hashtable<String, String> headers = new Hashtable<>();
732                 ZipInputStream zis = new ZipInputStream(is);
733                 ZipEntry entry;
734                 while ((entry = zis.getNextEntry()) != null) {
735                     if (MANIFEST_NAME.equals(entry.getName())) {
736                         Attributes attributes = new Manifest(zis).getMainAttributes();
737                         for (Map.Entry attr : attributes.entrySet()) {
738                             headers.put(attr.getKey().toString(), attr.getValue().toString());
739                         }
740                     }
741                 }
742                 BundleRevision revision = new FakeBundleRevision(headers, uri, nextBundleId.incrementAndGet());
743                 Bundle bundle = revision.getBundle();
744                 MapUtils.addToMapSet(dstate.bundlesPerRegion, region, bundle.getBundleId());
745                 dstate.bundles.put(bundle.getBundleId(), bundle);
746                 return bundle;
747             } catch (IOException e) {
748                 throw new BundleException("Unable to install bundle", e);
749             }
750         }
751
752         @Override
753         public void updateBundle(Bundle bundle, String uri, InputStream is) throws BundleException {
754             throw new UnsupportedOperationException();
755         }
756
757         @Override
758         public void uninstall(Bundle bundle) throws BundleException {
759             throw new UnsupportedOperationException();
760         }
761
762         @Override
763         public void startBundle(Bundle bundle) throws BundleException {
764         }
765
766         @Override
767         public void stopBundle(Bundle bundle, int options) throws BundleException {
768         }
769
770         @Override
771         public void setBundleStartLevel(Bundle bundle, int startLevel) {
772         }
773
774         @Override
775         public void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
776         }
777
778         @Override
779         public void resolveBundles(Set<Bundle> bundles, Map<Resource, List<Wire>> wiring, Map<Resource, Bundle> resToBnd) {
780         }
781
782         @Override
783         public void replaceDigraph(Map<String, Map<String, Map<String, Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException {
784         }
785     }
786
787     public class MavenResolverLog extends org.apache.felix.resolver.Logger {
788
789         public MavenResolverLog() {
790             super(Logger.LOG_DEBUG);
791         }
792
793         @Override
794         protected void doLog(int level, String msg, Throwable throwable) {
795             switch (level) {
796             case LOG_DEBUG:
797                 getLog().debug(msg, throwable);
798                 break;
799             case LOG_INFO:
800                 getLog().info(msg, throwable);
801                 break;
802             case LOG_WARNING:
803                 getLog().warn(msg, throwable);
804                 break;
805             case LOG_ERROR:
806                 getLog().error(msg, throwable);
807                 break;
808             }
809         }
810     }
811 }