[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;
+    }
+
+}

Reply via email to