[KARAF-3387] Add a verify-features goal to validate the feature with a real OSGi resolution
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/506753e7 Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/506753e7 Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/506753e7 Branch: refs/heads/master Commit: 506753e75cfffd6bec435e093d2229d0954f38e6 Parents: 0bbbb0b Author: Guillaume Nodet <gno...@gmail.com> Authored: Thu Nov 20 15:32:15 2014 +0100 Committer: Guillaume Nodet <gno...@gmail.com> Committed: Thu Nov 27 11:49:19 2014 +0100 ---------------------------------------------------------------------- .../features/internal/service/Deployer.java | 43 +- tooling/karaf-maven-plugin/pom.xml | 13 + .../features/VerifyFeatureResolutionMojo.java | 765 +++++++++++++++++++ .../karaf/tooling/utils/PropertiesLoader.java | 249 ++++++ 4 files changed, 1052 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/506753e7/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java index 81cc0f8..3461115 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java @@ -130,27 +130,27 @@ public class Deployer { } } - static class DeploymentState { - State state; - Bundle serviceBundle; - int initialBundleStartLevel; - int currentStartLevel; - Map<Long, Bundle> bundles; - Map<String, Feature> features; - Map<String, Set<Long>> bundlesPerRegion; - Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion; + public static class DeploymentState { + public State state; + public Bundle serviceBundle; + public int initialBundleStartLevel; + public int currentStartLevel; + public Map<Long, Bundle> bundles; + public Map<String, Feature> features; + public Map<String, Set<Long>> bundlesPerRegion; + public Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion; } - static class DeploymentRequest { - Set<String> overrides; - String featureResolutionRange; - String bundleUpdateRange; - String updateSnaphots; - Repository globalRepository; + public static class DeploymentRequest { + public Set<String> overrides; + public String featureResolutionRange; + public String bundleUpdateRange; + public String updateSnaphots; + public Repository globalRepository; - Map<String, Set<String>> requirements; - Map<String, Map<String, FeaturesService.RequestedState>> stateChanges; - EnumSet<FeaturesService.Option> options; + public Map<String, Set<String>> requirements; + public Map<String, Map<String, FeaturesService.RequestedState>> stateChanges; + public EnumSet<FeaturesService.Option> options; } static class Deployment { @@ -953,6 +953,13 @@ public class Deployer { List<Resource> toDeploy = bundlesInRegion != null ? new ArrayList<>(bundlesInRegion) : new ArrayList<Resource>(); + // Remove the system bundle + Bundle systemBundle = dstate.bundles.get(0l); + if (systemBundle != null) { + // It may be null when unit testing, so ignore that + toDeploy.remove(systemBundle.adapt(BundleRevision.class)); + } + // First pass: go through all installed bundles and mark them // as either to ignore or delete for (long bundleId : managed) { http://git-wip-us.apache.org/repos/asf/karaf/blob/506753e7/tooling/karaf-maven-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/tooling/karaf-maven-plugin/pom.xml b/tooling/karaf-maven-plugin/pom.xml index 69d805d..c72fc35 100644 --- a/tooling/karaf-maven-plugin/pom.xml +++ b/tooling/karaf-maven-plugin/pom.xml @@ -134,6 +134,10 @@ </exclusions> </dependency> <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.resolver</artifactId> + </dependency> + <dependency> <groupId>org.ops4j.pax.url</groupId> <artifactId>pax-url-wrap</artifactId> <classifier>uber</classifier> @@ -143,6 +147,11 @@ <artifactId>pax-url-aether</artifactId> </dependency> <dependency> + <groupId>org.ops4j.pax.url</groupId> + <artifactId>pax-url-war</artifactId> + <classifier>uber</classifier> + </dependency> + <dependency> <groupId>org.apache.karaf.deployer</groupId> <artifactId>org.apache.karaf.deployer.spring</artifactId> </dependency> @@ -171,6 +180,10 @@ <artifactId>org.apache.karaf.shell.console</artifactId> </dependency> <dependency> + <groupId>org.apache.karaf.features</groupId> + <artifactId>org.apache.karaf.features.core</artifactId> + </dependency> + <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> <scope>compile</scope> http://git-wip-us.apache.org/repos/asf/karaf/blob/506753e7/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java ---------------------------------------------------------------------- diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java new file mode 100644 index 0000000..f03d923 --- /dev/null +++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/VerifyFeatureResolutionMojo.java @@ -0,0 +1,765 @@ +/* + * 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.apache.karaf.tooling.features; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicLong; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import aQute.bnd.osgi.Macro; +import aQute.bnd.osgi.Processor; +import org.apache.felix.utils.version.VersionRange; +import org.apache.felix.utils.version.VersionTable; +import org.apache.karaf.features.Conditional; +import org.apache.karaf.features.ConfigFileInfo; +import org.apache.karaf.features.Feature; +import org.apache.karaf.features.FeatureEvent; +import org.apache.karaf.features.FeaturesService; +import org.apache.karaf.features.Repository; +import org.apache.karaf.features.internal.download.DownloadManager; +import org.apache.karaf.features.internal.download.simple.SimpleDownloader; +import org.apache.karaf.features.internal.resolver.ResourceBuilder; +import org.apache.karaf.features.internal.resolver.ResourceImpl; +import org.apache.karaf.features.internal.resolver.ResourceUtils; +import org.apache.karaf.features.internal.service.Deployer; +import org.apache.karaf.features.internal.service.RepositoryImpl; +import org.apache.karaf.features.internal.service.State; +import org.apache.karaf.features.internal.util.MapUtils; +import org.apache.karaf.features.internal.util.MultiException; +import org.apache.karaf.tooling.utils.PropertiesLoader; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.ops4j.pax.url.mvn.ServiceConstants; +import org.ops4j.pax.url.mvn.internal.Connection; +import org.ops4j.pax.url.mvn.internal.config.MavenConfigurationImpl; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.Version; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.startlevel.BundleStartLevel; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.resource.Requirement; +import org.osgi.service.resolver.ResolutionException; +import shaded.org.ops4j.util.property.PropertiesPropertyResolver; + +import static java.util.jar.JarFile.MANIFEST_NAME; + +@Mojo(name = "verify-features", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) +public class VerifyFeatureResolutionMojo extends AbstractMojo { + + @Parameter(property = "descriptors") + protected Set<String> descriptors; + + @Parameter(property = "features") + protected Set<String> features; + + @Parameter(property = "framework") + protected Set<String> framework; + + @Parameter(property = "configuration") + protected String configuration; + + @Parameter(property = "distribution", defaultValue = "org.apache.karaf:apache-karaf") + protected String distribution; + + @Parameter(property = "javase") + protected String javase; + + @Parameter(property = "dist-dir") + protected String distDir; + + @Parameter(property = "additional-metadata") + protected File additionalMetadata; + + @Parameter(property = "ignore-missing-conditions") + protected boolean ignoreMissingConditions; + + @Parameter(property = "fail") + protected String fail = "end"; + + @Parameter(property = "verify-transitive") + protected boolean verifyTransitive = false; + + @Parameter(defaultValue = "${project}", readonly = true) + protected MavenProject project; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + try { + Field field = URL.class.getDeclaredField("factory"); + field.setAccessible(true); + field.set(null, null); + } catch (Exception e) { + e.printStackTrace(); + } + URL.setURLStreamHandlerFactory(new CustomBundleURLStreamHandlerFactory()); + + System.setProperty("karaf.home", "target/karaf"); + System.setProperty("karaf.data", "target/karaf/data"); + + + Hashtable<String, String> properties = new Hashtable<>(); + + if (additionalMetadata != null) { + try (Reader reader = new FileReader(additionalMetadata)) { + Properties metadata = new Properties(); + metadata.load(reader); + for (Enumeration<?> e = metadata.propertyNames(); e.hasMoreElements(); ) { + Object key = e.nextElement(); + Object val = metadata.get(key); + properties.put(key.toString(), val.toString()); + } + } catch (IOException e) { + throw new MojoExecutionException("Unable to load additional metadata from " + additionalMetadata, e); + } + } + + DownloadManager manager = new SimpleDownloader(); + final Map<String, Repository> repositories; + Map<String, Feature[]> allFeatures = new HashMap<>(); + try { + repositories = loadRepositories(manager, descriptors); + for (String repoUri : repositories.keySet()) { + Feature[] features = repositories.get(repoUri).getFeatures(); + // Ack features to inline configuration files urls + for (Feature feature : features) { + for (org.apache.karaf.features.BundleInfo bi : feature.getBundles()) { + String loc = bi.getLocation(); + String nloc = null; + if (loc.contains("file:")) { + for (ConfigFileInfo cfi : feature.getConfigurationFiles()) { + if (cfi.getFinalname().substring(1) + .equals(loc.substring(loc.indexOf("file:") + "file:".length()))) { + nloc = cfi.getLocation(); + } + } + } + if (nloc != null) { + Field field = bi.getClass().getDeclaredField("location"); + field.setAccessible(true); + field.set(bi, loc.substring(0, loc.indexOf("file:")) + nloc); + } + } + } + allFeatures.put(repoUri, features); + } + } catch (Exception e) { + throw new MojoExecutionException("Unable to load features descriptors", e); + } + + List<Feature> featuresToTest = new ArrayList<>(); + if (verifyTransitive) { + for (Feature[] features : allFeatures.values()) { + featuresToTest.addAll(Arrays.asList(features)); + } + } else { + for (String uri : descriptors) { + featuresToTest.addAll(Arrays.asList(allFeatures.get(uri))); + } + } + if (features != null && !features.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (String feature : features) { + if (sb.length() > 0) { + sb.append("|"); + } + String p = feature.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*"); + sb.append(p); + if (!feature.contains("/")) { + sb.append("/.*"); + } + } + Pattern pattern = Pattern.compile(sb.toString()); + for (Iterator<Feature> iterator = featuresToTest.iterator(); iterator.hasNext();) { + Feature feature = iterator.next(); + String id = feature.getName() + "/" + feature.getVersion(); + if (!pattern.matcher(id).matches()) { + iterator.remove(); + } + } + } + + for (String fmk : framework) { + properties.put("feature.framework." + fmk, fmk); + } + List<Exception> failures = new ArrayList<>(); + for (Feature feature : featuresToTest) { + try { + String id = feature.getName() + "/" + feature.getVersion(); + verifyResolution(manager, repositories, Collections.singleton(id), properties); + getLog().info("Verification of feature " + id + " succeeded"); + } catch (Exception e) { + if (e.getCause() instanceof ResolutionException) { + getLog().warn(e.getMessage()); + } else { + getLog().warn(e); + } + failures.add(e); + if ("first".equals(fail)) { + throw e; + } + } + for (Conditional cond : feature.getConditional()) { + Set<String> ids = new LinkedHashSet<>(); + ids.add(feature.getId()); + ids.addAll(cond.getCondition()); + try { + verifyResolution(manager, repositories, ids, properties); + getLog().info("Verification of feature " + ids + " succeeded"); + } catch (Exception e) { + if (ignoreMissingConditions && e.getCause() instanceof ResolutionException) { + boolean ignore = true; + Collection<Requirement> requirements = ((ResolutionException) e.getCause()).getUnresolvedRequirements(); + for (Requirement req : requirements) { + ignore &= (IdentityNamespace.IDENTITY_NAMESPACE.equals(req.getNamespace()) + && ResourceUtils.TYPE_FEATURE.equals(req.getAttributes().get("type")) + && cond.getCondition().contains(req.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE).toString())); + } + if (ignore) { + getLog().warn("Feature resolution failed for " + ids + + "\nMessage: " + e.getCause().getMessage()); + continue; + } + } + if (e.getCause() instanceof ResolutionException) { + getLog().warn(e.getMessage()); + } else { + getLog().warn(e); + } + failures.add(e); + if ("first".equals(fail)) { + throw e; + } + } + } + } + if ("end".equals(fail) && !failures.isEmpty()) { + throw new MojoExecutionException("Verification failures", new MultiException("Verification failures", failures)); + } + } + + private void verifyResolution(DownloadManager manager, final Map<String, Repository> repositories, Set<String> features, Hashtable<String, String> properties) throws MojoExecutionException { + try { + Bundle systemBundle = getSystemBundle(getMetadata(properties, "metadata#")); + DummyDeployCallback callback = new DummyDeployCallback(systemBundle, repositories.values()); + Deployer deployer = new Deployer(manager, callback); + + + // Install framework + Deployer.DeploymentRequest request = createDeploymentRequest(); + + for (String fmwk : framework) { + MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, fmwk); + } + try { + deployer.deploy(callback.getDeploymentState(), request); + } catch (Exception e) { + throw new MojoExecutionException("Unable to resolve framework features", e); + } + + + /* + boolean resolveOptionalImports = getResolveOptionalImports(properties); + + DeploymentBuilder builder = new DeploymentBuilder( + manager, + null, + repositories.values(), + -1 // Disable url handlers + ); + Map<String, Resource> downloadedResources = builder.download( + getPrefixedProperties(properties, "feature."), + getPrefixedProperties(properties, "bundle."), + getPrefixedProperties(properties, "fab."), + getPrefixedProperties(properties, "req."), + getPrefixedProperties(properties, "override."), + getPrefixedProperties(properties, "optional."), + getMetadata(properties, "metadata#") + ); + + for (String uri : getPrefixedProperties(properties, "resources.")) { + builder.addResourceRepository(new MetadataRepository(new HttpMetadataProvider(uri))); + } + */ + + + // Install features + for (String feature : features) { + MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, feature); + } + try { + Set<String> prereqs = new HashSet<>(); + while (true) { + try { + deployer.deploy(callback.getDeploymentState(), request); + break; + } catch (Deployer.PartialDeploymentException e) { + if (!prereqs.containsAll(e.getMissing())) { + prereqs.addAll(e.getMissing()); + } else { + throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing()); + } + } + } + // TODO: find unused resources ? + } catch (Exception e) { + throw new MojoExecutionException("Feature resolution failed for " + features + + "\nMessage: " + e.getMessage() + + "\nRepositories: " + toString(new TreeSet<>(repositories.keySet())) + + "\nResources: " + toString(new TreeSet<>(manager.getProviders().keySet())), e); + } + + + } catch (MojoExecutionException e) { + throw e; + } catch (Exception e) { + throw new MojoExecutionException("Error verifying feature " + features + "\nMessage: " + e.getMessage(), e); + } + } + + private Deployer.DeploymentRequest createDeploymentRequest() { + Deployer.DeploymentRequest request = new Deployer.DeploymentRequest(); + request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE; + request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE; + request.overrides = new HashSet<>(); + request.requirements = new HashMap<>(); + request.stateChanges = new HashMap<>(); + request.options = EnumSet.noneOf(FeaturesService.Option.class); + return request; + } + + private String toString(Collection<String> collection) { + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + for (String s : collection) { + sb.append("\t").append(s).append("\n"); + } + sb.append("}"); + return sb.toString(); + } + + private Bundle getSystemBundle(Map<String, Map<VersionRange, Map<String, String>>> metadata) throws Exception { + URL configPropURL; + if (configuration != null) { + configPropURL = new URL(configuration); + } else { + Artifact karafDistro = project.getArtifactMap().get(distribution); + if (karafDistro == null) { + throw new MojoFailureException("The karaf distribution " + distribution + " is not a dependency"); + } + if ("kar".equals(karafDistro.getType()) && distDir == null) { + distDir = "resources"; + } + String dir = distDir; + if (dir == null) { + dir = karafDistro.getArtifactId() + "-" + karafDistro.getBaseVersion(); + } + configPropURL = new URL("jar:file:" + karafDistro.getFile() + "!/" + dir + "/etc/config.properties"); + } + org.apache.felix.utils.properties.Properties configProps = PropertiesLoader.loadPropertiesFile(configPropURL, true); +// copySystemProperties(configProps); + if (javase == null) { + configProps.put("java.specification.version", System.getProperty("java.specification.version")); + } else { + configProps.put("java.specification.version", javase); + } + configProps.substitute(); + + Attributes attributes = new Attributes(); + attributes.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); + attributes.putValue(Constants.BUNDLE_SYMBOLICNAME, "system.bundle"); + attributes.putValue(Constants.BUNDLE_VERSION, "0.0.0"); + + String exportPackages = configProps.getProperty("org.osgi.framework.system.packages"); + if (configProps.containsKey("org.osgi.framework.system.packages.extra")) { + exportPackages += "," + configProps.getProperty("org.osgi.framework.system.packages.extra"); + } + exportPackages = exportPackages.replaceAll(",\\s*,", ","); + attributes.putValue(Constants.EXPORT_PACKAGE, exportPackages); + + String systemCaps = configProps.getProperty("org.osgi.framework.system.capabilities"); + attributes.putValue(Constants.PROVIDE_CAPABILITY, systemCaps); + + // TODO: support metadata overrides on system bundle +// attributes = DeploymentBuilder.overrideAttributes(attributes, metadata); + + final Hashtable<String, String> headers = new Hashtable<>(); + for (Map.Entry attr : attributes.entrySet()) { + headers.put(attr.getKey().toString(), attr.getValue().toString()); + } + + final FakeBundleRevision resource = new FakeBundleRevision(headers, "system-bundle", 0l); + return resource.getBundle(); + } + + + public static Map<String, Repository> loadRepositories(DownloadManager manager, Set<String> uris) throws Exception { + // TODO: use downloader + final Map<String, Repository> repositories = new HashMap<>(); + for (String uri : uris) { + doLoadRepository(manager, repositories, URI.create(uri)); + } + return repositories; + } + + private static void doLoadRepository(DownloadManager manager, Map<String, Repository> repositories, URI uri) throws IOException { + if (!repositories.containsKey(uri.toString())) { + RepositoryImpl repository = new RepositoryImpl(uri); + repository.load(true); + repositories.put(uri.toString(), repository); + for (URI dep : repository.getRepositories()) { + doLoadRepository(manager, repositories, dep); + } + } + } + + public static Set<String> getPrefixedProperties(Map<String, String> properties, String prefix) { + Set<String> result = new HashSet<>(); + for (String key : properties.keySet()) { + if (key.startsWith(prefix)) { + String url = properties.get(key); + if (url == null || url.length() == 0) { + url = key.substring(prefix.length()); + } + if (!url.isEmpty()) { + result.add(url); + } + } + } + return result; + } + + public static class CustomBundleURLStreamHandlerFactory implements URLStreamHandlerFactory { + + public URLStreamHandler createURLStreamHandler(String protocol) { + switch (protocol) { + case "mvn": + return new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) throws IOException { + PropertiesPropertyResolver propertyResolver = new PropertiesPropertyResolver(System.getProperties()); + final MavenConfigurationImpl config = new MavenConfigurationImpl(propertyResolver, ServiceConstants.PID); + return new Connection(url, config); + } + }; + case "wrap": + return new org.ops4j.pax.url.wrap.Handler(); + case "blueprint": + return new org.apache.karaf.deployer.blueprint.BlueprintURLHandler(); + case "war": + return new org.ops4j.pax.url.war.Handler(); + default: + return null; + } + } + + } + + public static Map<String, Map<VersionRange, Map<String, String>>> getMetadata(Map<String, String> properties, String prefix) { + Map<String, Map<VersionRange, Map<String, String>>> result = new HashMap<>(); + for (String key : properties.keySet()) { + if (key.startsWith(prefix)) { + String val = properties.get(key); + key = key.substring(prefix.length()); + String[] parts = key.split("#"); + if (parts.length == 3) { + Map<VersionRange, Map<String, String>> ranges = result.get(parts[0]); + if (ranges == null) { + ranges = new HashMap<>(); + result.put(parts[0], ranges); + } + String version = parts[1]; + if (!version.startsWith("[") && !version.startsWith("(")) { + Processor processor = new Processor(); + processor.setProperty("@", VersionTable.getVersion(version).toString()); + Macro macro = new Macro(processor); + version = macro.process("${range;[==,=+)}"); + } + VersionRange range = new VersionRange(version); + Map<String, String> hdrs = ranges.get(range); + if (hdrs == null) { + hdrs = new HashMap<>(); + ranges.put(range, hdrs); + } + hdrs.put(parts[2], val); + } + } + } + return result; + } + + public static class FakeBundleRevision extends ResourceImpl implements BundleRevision, BundleStartLevel { + + private final Bundle bundle; + private int startLevel; + + public FakeBundleRevision(final Hashtable<String, String> headers, final String location, final long bundleId) throws BundleException { + ResourceBuilder.build(this, location, headers); + this.bundle = (Bundle) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { Bundle.class }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("hashCode")) { + return FakeBundleRevision.this.hashCode(); + } else if (method.getName().equals("equals")) { + return proxy == args[0]; + } else if (method.getName().equals("toString")) { + return bundle.getSymbolicName() + "/" + bundle.getVersion(); + } else if (method.getName().equals("adapt")) { + if (args.length == 1 && args[0] == BundleRevision.class) { + return FakeBundleRevision.this; + } else if (args.length == 1 && args[0] == BundleStartLevel.class) { + return FakeBundleRevision.this; + } + } else if (method.getName().equals("getHeaders")) { + return headers; + } else if (method.getName().equals("getBundleId")) { + return bundleId; + } else if (method.getName().equals("getLocation")) { + return location; + } else if (method.getName().equals("getSymbolicName")) { + String name = headers.get(Constants.BUNDLE_SYMBOLICNAME); + int idx = name.indexOf(';'); + if (idx > 0) { + name = name.substring(0, idx).trim(); + } + return name; + } else if (method.getName().equals("getVersion")) { + return new Version(headers.get(Constants.BUNDLE_VERSION)); + } else if (method.getName().equals("getState")) { + return Bundle.ACTIVE; + } else if (method.getName().equals("getLastModified")) { + return 0l; + } + return null; + } + }); + } + + @Override + public int getStartLevel() { + return startLevel; + } + + @Override + public void setStartLevel(int startLevel) { + this.startLevel = startLevel; + } + + @Override + public boolean isPersistentlyStarted() { + return true; + } + + @Override + public boolean isActivationPolicyUsed() { + return false; + } + + @Override + public String getSymbolicName() { + return bundle.getSymbolicName(); + } + + @Override + public Version getVersion() { + return bundle.getVersion(); + } + + @Override + public List<BundleCapability> getDeclaredCapabilities(String namespace) { + throw new UnsupportedOperationException(); + } + + @Override + public List<BundleRequirement> getDeclaredRequirements(String namespace) { + throw new UnsupportedOperationException(); + } + + @Override + public int getTypes() { + throw new UnsupportedOperationException(); + } + + @Override + public BundleWiring getWiring() { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle getBundle() { + return bundle; + } + } + + public static class DummyDeployCallback implements Deployer.DeployCallback { + + private final Bundle systemBundle; + private final Deployer.DeploymentState dstate; + private final AtomicLong nextBundleId = new AtomicLong(0); + + public DummyDeployCallback(Bundle sysBundle, Collection<Repository> repositories) throws Exception { + systemBundle = sysBundle; + dstate = new Deployer.DeploymentState(); + dstate.bundles = new HashMap<>(); + dstate.features = new HashMap<>(); + dstate.bundlesPerRegion = new HashMap<>(); + dstate.filtersPerRegion = new HashMap<>(); + dstate.state = new State(); + + MapUtils.addToMapSet(dstate.bundlesPerRegion, FeaturesService.ROOT_REGION, 0l); + dstate.bundles.put(0l, systemBundle); + for (Repository repo : repositories) { + for (Feature f : repo.getFeatures()) { + dstate.features.put(f.getId(), f); + } + } + } + + public Deployer.DeploymentState getDeploymentState() { + return dstate; + } + + @Override + public void print(String message, boolean verbose) { + } + + @Override + public void saveState(State state) { + dstate.state.replace(state); + } + + @Override + public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException { + } + + @Override + public void installFeatureConfigs(Feature feature) throws IOException, InvalidSyntaxException { + } + + @Override + public void callListeners(FeatureEvent featureEvent) { + } + + @Override + public Bundle installBundle(String region, String uri, InputStream is) throws BundleException { + try { + Hashtable<String, String> headers = new Hashtable<>(); + ZipInputStream zis = new ZipInputStream(is); + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (MANIFEST_NAME.equals(entry.getName())) { + Attributes attributes = new Manifest(zis).getMainAttributes(); + for (Map.Entry attr : attributes.entrySet()) { + headers.put(attr.getKey().toString(), attr.getValue().toString()); + } + } + } + BundleRevision revision = new FakeBundleRevision(headers, uri, nextBundleId.incrementAndGet()); + Bundle bundle = revision.getBundle(); + MapUtils.addToMapSet(dstate.bundlesPerRegion, region, bundle.getBundleId()); + dstate.bundles.put(bundle.getBundleId(), bundle); + return bundle; + } catch (IOException e) { + throw new BundleException("Unable to install bundle", e); + } + } + + @Override + public void updateBundle(Bundle bundle, InputStream is) throws BundleException { + throw new UnsupportedOperationException(); + } + + @Override + public void uninstall(Bundle bundle) throws BundleException { + throw new UnsupportedOperationException(); + } + + @Override + public void startBundle(Bundle bundle) throws BundleException { + } + + @Override + public void stopBundle(Bundle bundle, int options) throws BundleException { + } + + @Override + public void setBundleStartLevel(Bundle bundle, int startLevel) { + } + + @Override + public void refreshPackages(Collection<Bundle> bundles) throws InterruptedException { + } + + @Override + public void resolveBundles(Set<Bundle> bundles) { + } + + @Override + public void replaceDigraph(Map<String, Map<String, Map<String, Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException { + } + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/506753e7/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java ---------------------------------------------------------------------- diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java new file mode 100644 index 0000000..6507b83 --- /dev/null +++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/PropertiesLoader.java @@ -0,0 +1,249 @@ +/* + * 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.apache.karaf.tooling.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import org.apache.felix.utils.properties.Properties; +import java.util.StringTokenizer; + +import static org.apache.felix.utils.properties.InterpolationHelper.substVars; + +public class PropertiesLoader { + + private static final String INCLUDES_PROPERTY = "${includes}"; // mandatory includes + + private static final String OPTIONALS_PROPERTY = "${optionals}"; // optionals include + + private static final String OVERRIDE_PREFIX = "karaf.override."; // prefix that marks that system property should override defaults. + + /** + * <p> + * Loads the configuration properties in the configuration property file + * associated with the framework installation; these properties + * are accessible to the framework and to bundles and are intended + * for configuration purposes. By default, the configuration property + * file is located in the <tt>conf/</tt> directory of the Felix + * installation directory and is called "<tt>config.properties</tt>". + * The installation directory of Felix is assumed to be the parent + * directory of the <tt>felix.jar</tt> file as found on the system class + * path property. The precise file from which to load configuration + * properties can be set by initializing the "<tt>felix.config.properties</tt>" + * system property to an arbitrary URL. + * </p> + * + * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an error. + * @throws Exception if something wrong occurs + */ + public static Properties loadConfigProperties(File file) throws Exception { + // See if the property URL was specified as a property. + URL configPropURL; + try { + configPropURL = file.toURI().toURL(); + } + catch (MalformedURLException ex) { + System.err.print("Main: " + ex); + return null; + } + + Properties configProps = loadPropertiesFile(configPropURL, false); + copySystemProperties(configProps); + configProps.substitute(); + + // Perform variable substitution for system properties. +// for (Enumeration<?> e = configProps.propertyNames(); e.hasMoreElements();) { +// String name = (String) e.nextElement(); +// configProps.setProperty(name, +// SubstHelper.substVars(configProps.getProperty(name), name, null, configProps)); +// } + + return configProps; + } + + /** + * <p> + * Loads the properties in the system property file associated with the + * framework installation into <tt>System.setProperty()</tt>. These properties + * are not directly used by the framework in anyway. By default, the system + * property file is located in the <tt>conf/</tt> directory of the Felix + * installation directory and is called "<tt>system.properties</tt>". The + * installation directory of Felix is assumed to be the parent directory of + * the <tt>felix.jar</tt> file as found on the system class path property. + * The precise file from which to load system properties can be set by + * initializing the "<tt>felix.system.properties</tt>" system property to an + * arbitrary URL. + * </p> + * + * @param karafBase the karaf base folder + * @throws IOException + */ + public static void loadSystemProperties(File file) throws IOException { + Properties props = new Properties(false); + try { + InputStream is = new FileInputStream(file); + props.load(is); + is.close(); + } catch (Exception e1) { + // Ignore + } + + for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) { + String name = (String) e.nextElement(); + if (name.startsWith(OVERRIDE_PREFIX)) { + String overrideName = name.substring(OVERRIDE_PREFIX.length()); + String value = props.getProperty(name); + System.setProperty(overrideName, substVars(value, name, null, props)); + } else { + String value = System.getProperty(name, props.getProperty(name)); + System.setProperty(name, substVars(value, name, null, props)); + } + } + } + + public static void copySystemProperties(Properties configProps) { + for (Enumeration<?> e = System.getProperties().propertyNames(); + e.hasMoreElements();) { + String key = (String) e.nextElement(); + if (key.startsWith("felix.") || + key.startsWith("karaf.") || + key.startsWith("org.osgi.framework.")) { + configProps.setProperty(key, System.getProperty(key)); + } + } + } + + public static Properties loadPropertiesOrFail(File configFile) { + try { + URL configPropURL = configFile.toURI().toURL(); + return loadPropertiesFile(configPropURL, true); + } catch (Exception e) { + throw new RuntimeException("Error loading properties from " + configFile, e); + } + } + + public static Properties loadPropertiesFile(URL configPropURL, boolean failIfNotFound) throws Exception { + Properties configProps = new Properties(null, false); + InputStream is = null; + try { + is = configPropURL.openConnection().getInputStream(); + configProps.load(is); + is.close(); + } catch (FileNotFoundException ex) { + if (failIfNotFound) { + throw ex; + } else { + System.err.println("WARN: " + configPropURL + " is not found, so not loaded"); + } + } catch (Exception ex) { + System.err.println("Error loading config properties from " + configPropURL); + System.err.println("Main: " + ex); + return configProps; + } finally { + try { + if (is != null) { + is.close(); + } + } + catch (IOException ex2) { + // Nothing we can do. + } + } + loadIncludes(INCLUDES_PROPERTY, true, configPropURL, configProps); + loadIncludes(OPTIONALS_PROPERTY, false, configPropURL, configProps); + trimValues(configProps); + return configProps; + } + + private static void loadIncludes(String propertyName, boolean mandatory, URL configPropURL, Properties configProps) + throws MalformedURLException, Exception { + String includes = (String) configProps.get(propertyName); + if (includes != null) { + StringTokenizer st = new StringTokenizer(includes, "\" ", true); + if (st.countTokens() > 0) { + String location; + do { + location = nextLocation(st); + if (location != null) { + URL url = new URL(configPropURL, location); + Properties props = loadPropertiesFile(url, mandatory); + configProps.putAll(props); + } + } + while (location != null); + } + } + configProps.remove(propertyName); + } + + private static void trimValues(Properties configProps) { + for (String key : configProps.keySet()) { + configProps.put(key, configProps.get(key).trim()); + } + } + + private static String nextLocation(StringTokenizer st) { + String retVal = null; + + if (st.countTokens() > 0) { + String tokenList = "\" "; + StringBuffer tokBuf = new StringBuffer(10); + String tok; + boolean inQuote = false; + boolean tokStarted = false; + boolean exit = false; + while ((st.hasMoreTokens()) && (!exit)) { + tok = st.nextToken(tokenList); + if (tok.equals("\"")) { + inQuote = !inQuote; + if (inQuote) { + tokenList = "\""; + } else { + tokenList = "\" "; + } + + } else if (tok.equals(" ")) { + if (tokStarted) { + retVal = tokBuf.toString(); + tokStarted = false; + tokBuf = new StringBuffer(10); + exit = true; + } + } else { + tokStarted = true; + tokBuf.append(tok.trim()); + } + } + + // Handle case where end of token stream and + // still got data + if ((!exit) && (tokStarted)) { + retVal = tokBuf.toString(); + } + } + + return retVal; + } + +}