You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2016/07/12 14:04:33 UTC

[1/2] brooklyn-server git commit: OSGi fallback resource loading

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 0d4fa31af -> 5e732612c


OSGi fallback resource loading


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/b0d30700
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/b0d30700
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/b0d30700

Branch: refs/heads/master
Commit: b0d3070045b48abba532645df8ae95c20055cfaf
Parents: dcb54a3
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Fri Jul 8 19:39:27 2016 +0300
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Tue Jul 12 16:16:27 2016 +0300

----------------------------------------------------------------------
 .../brooklyn/camp/brooklyn/RebindOsgiTest.java  |   1 +
 .../JavaBrooklynClassLoadingContext.java        |  16 +-
 .../brooklyn/util/core/ClassLoaderUtils.java    | 194 ++++++++++++------
 .../brooklyn/util/core/LoaderDispatcher.java    | 161 +++++++++++++++
 .../util/core/osgi/ContainerFramework.java      |  14 ++
 .../util/core/ClassLoaderUtilsTest.java         | 201 ++++++++++++++++---
 .../brooklyn/util/osgi/SystemFramework.java     |  18 ++
 .../brooklyn/rt/felix/EmbeddedFramework.java    |  22 ++
 8 files changed, 521 insertions(+), 106 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/RebindOsgiTest.java
----------------------------------------------------------------------
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/RebindOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/RebindOsgiTest.java
index 2f2f462..b4310e8 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/RebindOsgiTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/RebindOsgiTest.java
@@ -118,6 +118,7 @@ public class RebindOsgiTest extends AbstractYamlRebindTest {
         String appVersion = "0.1.0";
         String appCatalogFormat;
         if (useOsgi) {
+            TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OSGI_BUNDLE_PATH);
             appCatalogFormat = Joiner.on("\n").join(
                     "brooklyn.catalog:",
                     "  id: " + appSymbolicName,

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/JavaBrooklynClassLoadingContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/JavaBrooklynClassLoadingContext.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/JavaBrooklynClassLoadingContext.java
index e89501e..8eac147 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/JavaBrooklynClassLoadingContext.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/JavaBrooklynClassLoadingContext.java
@@ -21,13 +21,9 @@ package org.apache.brooklyn.core.mgmt.classloading;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
-import java.io.IOException;
 import java.net.URL;
-import java.util.Collections;
-import java.util.Enumeration;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider;
 import org.apache.brooklyn.util.core.ClassLoaderUtils;
 import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -90,7 +86,7 @@ public class JavaBrooklynClassLoadingContext extends AbstractBrooklynClassLoadin
             return cls;
         }
         try {
-            return (Maybe) Maybe.of(new ClassLoaderUtils(this, mgmt).loadClass(className));
+            return (Maybe) Maybe.of(new ClassLoaderUtils(loader, mgmt).loadClass(className));
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
             // return original error
@@ -133,17 +129,11 @@ public class JavaBrooklynClassLoadingContext extends AbstractBrooklynClassLoadin
 
     @Override
     public URL getResource(String name) {
-        return getClassLoader().getResource(name);
+        return new ClassLoaderUtils(loader).getResource(name);
     }
 
     @Override
     public Iterable<URL> getResources(String name) {
-        Enumeration<URL> resources;
-        try {
-            resources = getClassLoader().getResources(name);
-        } catch (IOException e) {
-            throw Exceptions.propagate(e);
-        }
-        return Collections.list(resources);
+        return new ClassLoaderUtils(loader).getResources(name);
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
index 179cc4f..5423ba9 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
@@ -17,6 +17,7 @@ package org.apache.brooklyn.util.core;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.net.URL;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -31,11 +32,12 @@ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.util.core.LoaderDispatcher.ClassLoaderDispatcher;
+import org.apache.brooklyn.util.core.LoaderDispatcher.MultipleResourceLoaderDispatcher;
+import org.apache.brooklyn.util.core.LoaderDispatcher.ResourceLoaderDispatcher;
 import org.apache.brooklyn.util.core.osgi.Osgis;
-import org.apache.brooklyn.util.core.osgi.SystemFrameworkLoader;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.osgi.SystemFramework;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
@@ -44,7 +46,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
 
 public class ClassLoaderUtils {
     
@@ -73,37 +78,43 @@ public class ClassLoaderUtils {
 
     public ClassLoaderUtils(Class<?> callingClass) {
         checkNotNull(callingClass, "callingClass");
-        this.classLoader = (callingClass.getClassLoader() != null) ? callingClass.getClassLoader() : getClass().getClassLoader();
+        this.classLoader = getValidClassLoader(callingClass.getClassLoader());
         this.entity = null;
         this.mgmt = null;
     }
 
-    public ClassLoaderUtils(ClassLoader cl) {
-        this.classLoader = checkNotNull(cl, "classLoader");
+    public ClassLoaderUtils(@Nullable ClassLoader cl) {
+        this.classLoader = getValidClassLoader(cl);
         this.entity = null;
         this.mgmt = null;
     }
 
-    public ClassLoaderUtils(ClassLoader cl, @Nullable ManagementContext mgmt) {
-        this.classLoader = checkNotNull(cl, "classLoader");
+    public ClassLoaderUtils(@Nullable ClassLoader cl, @Nullable ManagementContext mgmt) {
+        this.classLoader = getValidClassLoader(cl);
         this.entity = null;
         this.mgmt = checkNotNull(mgmt, "mgmt");
     }
 
     public ClassLoaderUtils(Class<?> callingClass, Entity entity) {
         checkNotNull(callingClass, "callingClass");
-        this.classLoader = (callingClass.getClassLoader() != null) ? callingClass.getClassLoader() : getClass().getClassLoader();
+        this.classLoader = getValidClassLoader(callingClass.getClassLoader());
         this.entity = checkNotNull(entity, "entity");
         this.mgmt = ((EntityInternal)entity).getManagementContext();
     }
 
     public ClassLoaderUtils(Class<?> callingClass, @Nullable ManagementContext mgmt) {
         checkNotNull(callingClass, "callingClass");
-        this.classLoader = (callingClass.getClassLoader() != null) ? callingClass.getClassLoader() : getClass().getClassLoader();
+        this.classLoader = getValidClassLoader(callingClass.getClassLoader());
         this.entity = null;
         this.mgmt = checkNotNull(mgmt, "mgmt");
     }
 
+    // class.getClassLoader() could return null for classes on the boot class path,
+    // provide an alternative in this case.
+    protected ClassLoader getValidClassLoader(ClassLoader cl) {
+        return (cl != null) ? cl : getClass().getClassLoader();
+    }
+
     /**
      * Loads the given class, handle OSGi bundles. The class could be in one of the following formats:
      * <ul>
@@ -136,6 +147,48 @@ public class ClassLoaderUtils {
      * {@link #WHITE_LIST_KEY}, defaulting to all {@code org.apache.brooklyn.*} bundles.
      */
     public Class<?> loadClass(String name) throws ClassNotFoundException {
+        Maybe<Class<?>> cls = load(name, ClassLoaderDispatcher.INSTANCE);
+        if (cls.isPresent()) {
+            return cls.get();
+        } else {
+            throw new ClassNotFoundException("Class " + name + " not found on the application class path, nor in the bundle white list.", getReturnException(cls));
+        }
+    }
+
+    public Class<?> loadClass(String symbolicName, @Nullable String version, String className) throws ClassNotFoundException {
+        try {
+            return tryLoadFromBundle(ClassLoaderDispatcher.INSTANCE, symbolicName, version, className).get();
+        } catch (IllegalStateException e) {
+            throw new ClassNotFoundException("Class " + className + " could not be loaded from bundle " + toBundleString(symbolicName, version), e);
+        }
+    }
+
+    /**
+     * Finds the resource with the given name.
+     * @see {@link #loadClass(String)} for loading order
+     *
+     * @return null when no resource is found
+     */
+    public URL getResource(String name) {
+        return load(stripLeadingSlash(name), ResourceLoaderDispatcher.INSTANCE).orNull();
+    }
+
+    /**
+     * Finds all the resources with the given name. Aborts going through subsequent fallbacks when it finds at least one resource.
+     * @see {@link #loadClass(String)} for loading order
+     * 
+     * @return empty {@link Iterable} when no resources find
+     */
+    public Iterable<URL> getResources(String name) {
+        Maybe<Iterable<URL>> ret = load(stripLeadingSlash(name), MultipleResourceLoaderDispatcher.INSTANCE);
+        if (ret.isPresent()) {
+            return ret.get();
+        } else {
+            return ImmutableList.of();
+        }
+    }
+
+    protected <T> Maybe<T> load(String name, LoaderDispatcher<T> dispatcher) {
         String symbolicName;
         String version;
         String className;
@@ -161,21 +214,21 @@ public class ClassLoaderUtils {
             className = name;
         }
 
+        Maybe<T> cls;
         if (symbolicName != null && version != null) {
             // Very explicit; do as we're told!
-            return loadClass(symbolicName, version, className);
+            return tryLoadFromBundle(dispatcher, symbolicName, version, className);
         }
-        
+
         if (entity != null && mgmt != null) {
             String catalogItemId = entity.getCatalogItemId();
             if (catalogItemId != null) {
                 CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogItemId);
                 if (item != null) {
                     BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, item);
-                    try {
-                        return loader.loadClass(className);
-                    } catch (IllegalStateException e) {
-                        propagateIfCauseNotClassNotFound(e);
+                    cls = dispatcher.tryLoadFrom(loader, className);
+                    if (cls.isPresent()) {
+                        return cls;
                     }
                 } else {
                     log.warn("Entity " + entity + " refers to non-existent catalog item " + catalogItemId + ". Trying to load class " + name);
@@ -183,50 +236,34 @@ public class ClassLoaderUtils {
             }
         }
 
-        Class<?> cls = tryLoadFromBundleWhiteList(className);
-        if (cls != null) {
+        cls = tryLoadFromBundleWhiteList(dispatcher, className);
+        if (cls.isPresent()) {
             return cls;
         }
-        
-        try {
-            // Used instead of callingClass.getClassLoader().loadClass(...) as it could be null (only for bootstrap classes)
-            // Note that Class.forName(name, false, classLoader) doesn't seem to like us returning a 
-            // class with a different name from that intended (e.g. stripping off an OSGi prefix).
-            return classLoader.loadClass(className);
-        } catch (IllegalStateException e) {
-            propagateIfCauseNotClassNotFound(e);
-        } catch (ClassNotFoundException e) {
+
+        cls = dispatcher.tryLoadFrom(classLoader, className);
+        if (cls.isPresent()) {
+            return cls;
         }
 
         if (mgmt != null) {
-            try {
-                return mgmt.getCatalogClassLoader().loadClass(name);
-            } catch (IllegalStateException e) {
-                propagateIfCauseNotClassNotFound(e);
-            } catch (ClassNotFoundException e) {
+            cls = dispatcher.tryLoadFrom(mgmt.getCatalogClassLoader(), className);
+            if (cls.isPresent()) {
+                return cls;
             }
         }
 
         if (symbolicName != null) {
-            // Finally fall back to loading from any version of the bundle
-            return loadClass(symbolicName, version, className);
-        } else {
-            throw new ClassNotFoundException("Class " + name + " not found on the application class path, nor in the bundle white list.");
+            cls = tryLoadFromBundle(dispatcher, symbolicName, version, className);
+            if (cls.isPresent()) {
+                return cls;
+            }
         }
-    }
 
-    protected void propagateIfCauseNotClassNotFound(IllegalStateException e) {
-        // TODO loadClass() should not throw IllegalStateException; should throw ClassNotFoundException without wrapping.
-        ClassNotFoundException cnfe = Exceptions.getFirstThrowableOfType(e, ClassNotFoundException.class);
-        NoClassDefFoundError ncdfe = Exceptions.getFirstThrowableOfType(e, NoClassDefFoundError.class);
-        if (cnfe == null && ncdfe == null) {
-            throw e;
-        } else {
-            // ignore, try next way of loading
-        }
+        return Maybe.absentNull();
     }
-    
-    public Class<?> loadClass(String symbolicName, @Nullable String version, String className) throws ClassNotFoundException {
+
+    protected <T> Maybe<T> tryLoadFromBundle(LoaderDispatcher<T> dispatcher, String symbolicName, String version, String name) {
         Framework framework = getFramework();
         if (framework != null) {
             Maybe<Bundle> bundle = Osgis.bundleFinder(framework)
@@ -241,11 +278,11 @@ public class ClassLoaderUtils {
                     .find();
             }
             if (bundle.isAbsent()) {
-                throw new IllegalStateException("Bundle " + symbolicName + ":" + (version != null ? version : "any") + " not found to load class " + className);
+                throw new IllegalStateException("Bundle " + toBundleString(symbolicName, version) + " not found to load " + name);
             }
-            return SystemFrameworkLoader.get().loadClassFromBundle(className, bundle.get());
+            return dispatcher.tryLoadFrom(bundle.get(), name);
         } else {
-            return Class.forName(className, true, classLoader);
+            return dispatcher.tryLoadFrom(classLoader, name);
         }
     }
 
@@ -255,7 +292,7 @@ public class ClassLoaderUtils {
         return p.apply(bundle);
     }
 
-    protected Framework getFramework() {
+    private Framework getFramework() {
         if (mgmt != null) {
             Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager();
             if (osgiManager.isPresent()) {
@@ -279,7 +316,7 @@ public class ClassLoaderUtils {
     }
 
 
-    private static class WhiteListBundlePredicate implements Predicate<Bundle> {
+    protected static class WhiteListBundlePredicate implements Predicate<Bundle> {
         private final Pattern symbolicName;
         private final Pattern version;
 
@@ -295,23 +332,21 @@ public class ClassLoaderUtils {
         }
     }
 
-    private Class<?> tryLoadFromBundleWhiteList(String name) {
+    protected <T> Maybe<T> tryLoadFromBundleWhiteList(LoaderDispatcher<T> dispatcher, String className) {
         Framework framework = getFramework();
         if (framework == null) {
-            return null;
+            return Maybe.absentNull();
         }
         List<Bundle> bundles = Osgis.bundleFinder(framework)
             .satisfying(createBundleMatchingPredicate())
             .findAll();
-        SystemFramework bundleLoader = SystemFrameworkLoader.get();
         for (Bundle b : bundles) {
-            try {
-                return bundleLoader.loadClassFromBundle(name, b);
-            } catch (Exception e) {
-                Exceptions.propagateIfFatal(e);
+            Maybe<T> item = dispatcher.tryLoadFrom(b, className);
+            if (item.isPresent()) {
+                return item;
             }
         }
-        return null;
+        return Maybe.absentNull();
     }
 
     protected WhiteListBundlePredicate createBundleMatchingPredicate() {
@@ -326,4 +361,43 @@ public class ClassLoaderUtils {
         }
         return new WhiteListBundlePredicate(symbolicName, version);
     }
+
+    private String toBundleString(String symbolicName, String version) {
+        return symbolicName + ":" + (version != null ? version : "any");
+    }
+
+    /**
+     * cls should always be isAbsent()
+     * @return null for cls.isNull(). Otherwise return the exception that's contained in the Maybe.
+     */
+    protected Exception getReturnException(Maybe<Class<?>> cls) {
+        if (cls.isNull()) {
+            return null;
+        }
+        try {
+            cls.get();
+            return null;
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            return e;
+        }
+    }
+
+    // ClassLoader.getResource returns null if there's a leading slash
+    private String stripLeadingSlash(String name) {
+        String[] arr = name.split(":");
+        int last = arr.length - 1;
+        if (arr[last].startsWith("/")) {
+            arr[last] = arr[last].substring(1);
+        }
+        return Joiner.on(":").join(arr);
+    }
+    
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + Objects.toStringHelper(this)
+            .add("claddLoader", classLoader)
+            .add("entity", entity)
+            .add("mgmt", mgmt) + "]";
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java
new file mode 100644
index 0000000..f45c6d1
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 The Apache Software Foundation.
+ *
+ * Licensed 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.brooklyn.util.core;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.util.core.osgi.SystemFrameworkLoader;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public interface LoaderDispatcher<T> {
+    Maybe<T> tryLoadFrom(Bundle bundle, String name);
+    Maybe<T> tryLoadFrom(BrooklynClassLoadingContext loader, String name);
+    Maybe<T> tryLoadFrom(ClassLoader loader, String name);
+
+    public class ClassLoaderDispatcher implements LoaderDispatcher<Class<?>> {
+        private static final Logger log = LoggerFactory.getLogger(LoaderDispatcher.class);
+        public static final ClassLoaderDispatcher INSTANCE = new ClassLoaderDispatcher();
+
+        @Override
+        public Maybe<Class<?>> tryLoadFrom(Bundle bundle, String className) {
+            try {
+                return Maybe.<Class<?>>of(SystemFrameworkLoader.get().loadClassFromBundle(className, bundle));
+            } catch (ClassNotFoundException e) {
+                return Maybe.absent("Failed to load class " + className + " from bundle " + bundle, e);
+            }
+        }
+
+        @Override
+        public Maybe<Class<?>> tryLoadFrom(BrooklynClassLoadingContext loader, String className) {
+            try {
+                return Maybe.<Class<?>>of(loader.loadClass(className));
+            } catch (IllegalStateException e) {
+                propagateIfCauseNotClassNotFound(e);
+                return Maybe.absent("Failed to load class " + className + " from loader " + loader, e);
+            }
+        }
+
+        @Override
+        public Maybe<Class<?>> tryLoadFrom(ClassLoader classLoader, String className) {
+            try {
+                // Note that Class.forName(name, false, classLoader) doesn't seem to like us returning a 
+                // class with a different name from that intended (e.g. stripping off an OSGi prefix).
+                return Maybe.<Class<?>>of(classLoader.loadClass(className));
+            } catch (IllegalStateException e) {
+                propagateIfCauseNotClassNotFound(e);
+                return Maybe.absent("Failed to load class " + className + " from class loader " + classLoader, e);
+            } catch (ClassNotFoundException e) {
+                return Maybe.absent("Failed to load class " + className + " from class loader " + classLoader, e);
+            }
+        }
+
+        private void propagateIfCauseNotClassNotFound(IllegalStateException e) {
+            // TODO loadClass() should not throw IllegalStateException; should throw ClassNotFoundException without wrapping.
+            ClassNotFoundException cnfe = Exceptions.getFirstThrowableOfType(e, ClassNotFoundException.class);
+            NoClassDefFoundError ncdfe = Exceptions.getFirstThrowableOfType(e, NoClassDefFoundError.class);
+            if (cnfe == null && ncdfe == null) {
+                throw e;
+            } else {
+                if (ncdfe != null) {
+                    log.debug("Class loading failure", ncdfe);
+                } else if (cnfe != null) {
+                    BundleException bundleException = Exceptions.getFirstThrowableOfType(cnfe, BundleException.class);
+                    // wiring problem
+                    if (bundleException != null) {
+                        log.debug("Class loading failure", cnfe);
+                    }
+                }
+                // ignore, try next way of loading
+            }
+        }
+
+    }
+
+    public class ResourceLoaderDispatcher implements LoaderDispatcher<URL> {
+        public static final ResourceLoaderDispatcher INSTANCE = new ResourceLoaderDispatcher();
+
+        @Override
+        public Maybe<URL> tryLoadFrom(Bundle bundle, String name) {
+            return Maybe.ofDisallowingNull(SystemFrameworkLoader.get().getResourceFromBundle(name, bundle));
+        }
+
+        @Override
+        public Maybe<URL> tryLoadFrom(BrooklynClassLoadingContext loader, String name) {
+            return Maybe.ofDisallowingNull(loader.getResource(name));
+        }
+
+        @Override
+        public Maybe<URL> tryLoadFrom(ClassLoader classLoader, String name) {
+            return Maybe.ofDisallowingNull(classLoader.getResource(name));
+        }
+
+    }
+
+    public class MultipleResourceLoaderDispatcher implements LoaderDispatcher<Iterable<URL>> {
+        public static final MultipleResourceLoaderDispatcher INSTANCE = new MultipleResourceLoaderDispatcher();
+
+        @Override
+        public Maybe<Iterable<URL>> tryLoadFrom(Bundle bundle, String name) {
+            try {
+                return emptyToMaybeNull(SystemFrameworkLoader.get().getResourcesFromBundle(name, bundle));
+            } catch (IOException e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+
+        @Override
+        public Maybe<Iterable<URL>> tryLoadFrom(BrooklynClassLoadingContext loader, String name) {
+            return emptyToMaybeNull(loader.getResources(name));
+        }
+
+        @Override
+        public Maybe<Iterable<URL>> tryLoadFrom(ClassLoader classLoader, String name) {
+            try {
+                return emptyToMaybeNull(classLoader.getResources(name));
+            } catch (IOException e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+        
+        private Maybe<Iterable<URL>> emptyToMaybeNull(Iterable<URL> iter) {
+            if (iter.iterator().hasNext()) {
+                return Maybe.of(iter);
+            } else {
+                return Maybe.absentNull();
+            }
+        }
+        
+        private Maybe<Iterable<URL>> emptyToMaybeNull(Enumeration<URL> res) {
+            if (res == null) {
+                return Maybe.absentNull();
+            } else {
+                return emptyToMaybeNull(Collections.list(res));
+            }
+        }
+
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/core/src/main/java/org/apache/brooklyn/util/core/osgi/ContainerFramework.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/ContainerFramework.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/ContainerFramework.java
index 874f369..6876c5b 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/ContainerFramework.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/ContainerFramework.java
@@ -15,6 +15,10 @@
  */
 package org.apache.brooklyn.util.core.osgi;
 
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
 import org.apache.brooklyn.util.osgi.SystemFramework;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
@@ -44,4 +48,14 @@ public class ContainerFramework implements SystemFramework {
         return c;
     }
 
+    @Override
+    public <T> URL getResourceFromBundle(String type, Bundle b) {
+        return b.getResource(type);
+    }
+
+    @Override
+    public <T> Enumeration<URL> getResourcesFromBundle(String type, Bundle b) throws IOException {
+        return b.getResources(type);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
index b5bfbc2..b0cce9e 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
@@ -18,24 +18,40 @@
  */
 package org.apache.brooklyn.util.core;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
+import java.net.URL;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.catalog.internal.CatalogBundleDto;
+import org.apache.brooklyn.core.catalog.internal.CatalogEntityItemDto;
+import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder;
+import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.entity.stock.BasicEntity;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.test.support.TestResourceUnavailableException;
 import org.apache.brooklyn.util.core.osgi.Osgis;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.maven.MavenArtifact;
+import org.apache.brooklyn.util.maven.MavenRetriever;
 import org.apache.brooklyn.util.osgi.OsgiTestResources;
-import org.dom4j.tree.AbstractEntity;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.launch.Framework;
 import org.testng.annotations.AfterMethod;
@@ -70,12 +86,12 @@ public class ClassLoaderUtilsTest {
     @Test
     public void testLoadClassNotInOsgi() throws Exception {
         ClassLoaderUtils clu = new ClassLoaderUtils(getClass());
-        assertEquals(clu.loadClass(getClass().getName()), getClass());
-        assertEquals(clu.loadClass(Entity.class.getName()), Entity.class);
-        assertEquals(clu.loadClass(AbstractEntity.class.getName()), AbstractEntity.class);
+        assertLoadSucceeds(clu, getClass().getName(), getClass());
+        assertLoadSucceeds(clu, Entity.class.getName(), Entity.class);
+        assertLoadSucceeds(clu, AbstractEntity.class.getName(), AbstractEntity.class);
         assertLoadFails(clu, "org.apache.brooklyn.this.name.does.not.Exist");
     }
-    
+
     @Test
     public void testLoadClassInOsgi() throws Exception {
         String bundlePath = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH;
@@ -86,14 +102,20 @@ public class ClassLoaderUtilsTest {
 
         mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
         Bundle bundle = installBundle(mgmt, bundleUrl);
-        
-        ClassLoaderUtils clu = new ClassLoaderUtils(getClass(), mgmt);
-        
-        assertLoadFails(clu, classname);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + classname).getName(), classname);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + bundle.getVersion()+":" + classname).getName(), classname);
+        @SuppressWarnings("unchecked")
+        Class<? extends Entity> clazz = (Class<? extends Entity>) bundle.loadClass(classname);
+        Entity entity = createSimpleEntity(bundleUrl, clazz);
+
+        ClassLoaderUtils cluMgmt = new ClassLoaderUtils(getClass(), mgmt);
+        ClassLoaderUtils cluClass = new ClassLoaderUtils(clazz);
+        ClassLoaderUtils cluEntity = new ClassLoaderUtils(getClass(), entity);
+
+        assertLoadFails(classname, cluMgmt);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + classname, clazz, cluMgmt, cluClass, cluEntity);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + bundle.getVersion()+":" + classname, clazz, cluMgmt, cluClass, cluEntity);
     }
-    
+
+
     @Test
     public void testLoadClassInOsgiWhiteList() throws Exception {
         String bundlePath = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH;
@@ -104,28 +126,38 @@ public class ClassLoaderUtilsTest {
 
         mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
         Bundle bundle = installBundle(mgmt, bundleUrl);
+        Class<?> clazz = bundle.loadClass(classname);
+        Entity entity = createSimpleEntity(bundleUrl, clazz);
         
         String whileList = bundle.getSymbolicName()+":"+bundle.getVersion();
         System.setProperty(ClassLoaderUtils.WHITE_LIST_KEY, whileList);
         
-        ClassLoaderUtils clu = new ClassLoaderUtils(getClass(), mgmt);
-        assertEquals(clu.loadClass(classname).getName(), classname);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + classname).getName(), classname);
+        ClassLoaderUtils cluMgmt = new ClassLoaderUtils(getClass(), mgmt);
+        ClassLoaderUtils cluClass = new ClassLoaderUtils(clazz);
+        ClassLoaderUtils cluEntity = new ClassLoaderUtils(getClass(), entity);
+        
+        assertLoadSucceeds(classname, clazz, cluMgmt, cluClass, cluEntity);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + classname, clazz, cluMgmt, cluClass, cluEntity);
     }
     
     @Test
     public void testLoadClassInOsgiCore() throws Exception {
-        Class<?> clazz = AbstractEntity.class;
+        Class<?> clazz = BasicEntity.class;
         String classname = clazz.getName();
         
         mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
         Bundle bundle = getBundle(mgmt, "org.apache.brooklyn.core");
+        Entity entity = createSimpleEntity(bundle.getLocation(), clazz);
         
-        ClassLoaderUtils clu = new ClassLoaderUtils(getClass(), mgmt);
-        assertEquals(clu.loadClass(classname), clazz);
-        assertEquals(clu.loadClass(classname), clazz);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + classname), clazz);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + bundle.getVersion() + ":" + classname), clazz);
+        ClassLoaderUtils cluMgmt = new ClassLoaderUtils(getClass(), mgmt);
+        ClassLoaderUtils cluClass = new ClassLoaderUtils(clazz);
+        ClassLoaderUtils cluNone = new ClassLoaderUtils(getClass());
+        ClassLoaderUtils cluEntity = new ClassLoaderUtils(getClass(), entity);
+        
+        assertLoadSucceeds(classname, clazz, cluMgmt, cluClass, cluNone, cluEntity);
+        assertLoadSucceeds(classname, clazz, cluMgmt, cluClass, cluNone, cluEntity);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + classname, clazz, cluMgmt, cluClass, cluNone, cluEntity);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + bundle.getVersion() + ":" + classname, clazz, cluMgmt, cluClass, cluNone, cluEntity);
     }
     
     @Test
@@ -136,11 +168,14 @@ public class ClassLoaderUtilsTest {
         mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
         Bundle bundle = getBundle(mgmt, "org.apache.brooklyn.api");
         
-        ClassLoaderUtils clu = new ClassLoaderUtils(getClass(), mgmt);
-        assertEquals(clu.loadClass(classname), clazz);
-        assertEquals(clu.loadClass(classname), clazz);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + classname), clazz);
-        assertEquals(clu.loadClass(bundle.getSymbolicName() + ":" + bundle.getVersion() + ":" + classname), clazz);
+        ClassLoaderUtils cluMgmt = new ClassLoaderUtils(getClass(), mgmt);
+        ClassLoaderUtils cluClass = new ClassLoaderUtils(clazz);
+        ClassLoaderUtils cluNone = new ClassLoaderUtils(getClass());
+        
+        assertLoadSucceeds(classname, clazz, cluMgmt, cluClass, cluNone);
+        assertLoadSucceeds(classname, clazz, cluMgmt, cluClass, cluNone);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + classname, clazz, cluMgmt, cluClass, cluNone);
+        assertLoadSucceeds(bundle.getSymbolicName() + ":" + bundle.getVersion() + ":" + classname, clazz, cluMgmt, cluClass, cluNone);
     }
     
     @Test
@@ -162,13 +197,12 @@ public class ClassLoaderUtilsTest {
         mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
         ClassLoaderUtils clu = new ClassLoaderUtils(getClass(), mgmt);
         
-        String bundleUrl = "http://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar";
+        String bundleUrl = MavenRetriever.localUrl(MavenArtifact.fromCoordinate("com.google.guava:guava:jar:18.0"));
         Bundle bundle = installBundle(mgmt, bundleUrl);
         String bundleName = bundle.getSymbolicName();
         
         String classname = bundleName + ":" + ImmutableList.class.getName();
-        Class<?> clazz = clu.loadClass(classname);
-        assertEquals(clazz, ImmutableList.class);
+        assertLoadSucceeds(clu, classname, ImmutableList.class);
     }
     
     private Bundle installBundle(ManagementContext mgmt, String bundleUrl) throws Exception {
@@ -186,12 +220,113 @@ public class ClassLoaderUtilsTest {
         return result.get();
     }
 
-    private void assertLoadFails(ClassLoaderUtils clu, String className) {
+    private void assertLoadSucceeds(String bundledClassName, Class<?> expectedClass, ClassLoaderUtils... clua) throws ClassNotFoundException {
+        for (ClassLoaderUtils clu : clua) {
+            try {
+                assertLoadSucceeds(clu, bundledClassName, expectedClass);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalStateException("Load failed for " + clu, e);
+            }
+        }
+    }
+
+    private void assertLoadSucceeds(ClassLoaderUtils clu, String bundledClassName, Class<?> expectedClass) throws ClassNotFoundException {
+        BundledName className = new BundledName(bundledClassName);
+
+        Class<?> cls = clu.loadClass(bundledClassName);
+        assertEquals(cls.getName(), className.name);
+        if (expectedClass != null) {
+            assertEquals(cls, expectedClass);
+        }
+
+        ClassLoader cl = cls.getClassLoader();
+        BundledName resource = className.toResource();
+        String bundledResource = resource.toString();
+        URL resourceUrl = cl.getResource(resource.name);
+        assertEquals(clu.getResource(bundledResource), resourceUrl);
+        assertEquals(clu.getResources(bundledResource), ImmutableList.of(resourceUrl));
+
+        BundledName rootResource = new BundledName(resource.bundle, resource.version, "/" + resource.name);
+        String rootBundledResource = rootResource.toString();
+        assertEquals(clu.getResource(rootBundledResource), resourceUrl);
+        assertEquals(clu.getResources(rootBundledResource), ImmutableList.of(resourceUrl));
+    }
+
+    private void assertLoadFails(String bundledClassName, ClassLoaderUtils... clua) {
+        for (ClassLoaderUtils clu : clua) {
+            assertLoadFails(clu, bundledClassName);
+        }
+    }
+
+    private void assertLoadFails(ClassLoaderUtils clu, String bundledClassName) {
+        BundledName className = new BundledName(bundledClassName);
+
         try {
-            clu.loadClass(className);
-            Asserts.shouldHaveFailedPreviously();
+            clu.loadClass(bundledClassName);
+            Asserts.shouldHaveFailedPreviously("Using loader " + clu);
         } catch (ClassNotFoundException e) {
-            Asserts.expectedFailureContains(e, className, "not found on the application class path, nor in the bundle white list");
+            Asserts.expectedFailureContains(e, bundledClassName, "not found on the application class path, nor in the bundle white list");
         }
+
+        BundledName resource = className.toResource();
+        String bundledResource = resource.toString();
+        assertNull(clu.getResource(bundledResource), resource + " is supposed to fail resource loading, but it was successful");
+        assertEquals(clu.getResources(bundledResource), ImmutableList.of(), resource + " is supposed to fail resource loading, but it was successful");
     }
+
+    protected Entity createSimpleEntity(String bundleUrl, Class<?> clazz) {
+        @SuppressWarnings("unchecked")
+        Class<? extends Entity> entityClass = (Class<? extends Entity>) clazz;
+        EntitySpec<?> spec = EntitySpec.create(entityClass);
+        Entity entity = mgmt.getEntityManager().createEntity(spec);
+        CatalogEntityItemDto item = CatalogItemBuilder.newEntity(clazz.getName(), "1.0.0")
+                .libraries(ImmutableList.<CatalogBundle>of(new CatalogBundleDto(null, null, bundleUrl)))
+                .plan("{\"services\":[{\"type\": \"" + clazz.getName() + "\"}]}")
+                .build();
+        mgmt.getCatalog().addItem(item);
+        ((EntityInternal)entity).setCatalogItemId(item.getId());
+        return entity;
+    }
+
+    private static class BundledName {
+        String bundle;
+        String version;
+        String name;
+        BundledName(String bundledName) {
+            String[] arr = bundledName.split(":");
+            if (arr.length == 1) {
+                bundle = null;
+                version = null;
+                name = arr[0];
+            } else if (arr.length == 2) {
+                bundle = arr[0];
+                version = null;
+                name = arr[1];
+            } else if (arr.length == 3) {
+                bundle = arr[0];
+                version = arr[1];
+                name = arr[2];
+            } else {
+                throw new IllegalStateException("Invalid bundled name " + bundledName);
+            }
+        }
+        BundledName(@Nullable String bundle, @Nullable String version, String name) {
+            this.bundle = bundle;
+            this.version = version;
+            this.name = checkNotNull(name, "name");
+        }
+
+        @Override
+        public String toString() {
+            return (bundle != null ? bundle + ":" : "") +
+                    (version != null ? version + ":" : "") +
+                    name;
+        }
+
+        BundledName toResource() {
+            return new BundledName(bundle, version, name.replace(".", "/") + ".class");
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/utils/common/src/main/java/org/apache/brooklyn/util/osgi/SystemFramework.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/SystemFramework.java b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/SystemFramework.java
index 7b57a02..8a1b22b 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/SystemFramework.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/SystemFramework.java
@@ -15,6 +15,10 @@
  */
 package org.apache.brooklyn.util.osgi;
 
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
 import org.osgi.framework.Bundle;
 import org.osgi.framework.launch.Framework;
 
@@ -42,4 +46,18 @@ public interface SystemFramework {
      * requirements for loading classes so the implementation will abstract those.
      */
     public <T> Class<T> loadClassFromBundle(String type, Bundle b) throws ClassNotFoundException;
+
+    /**
+     * Loads a resource from the passed bundle. The embedded environment has some special
+     * requirements for loading resources so the implementation will abstract those.
+     */
+    public <T> URL getResourceFromBundle(String type, Bundle b);
+
+    /**
+     * Loads resources from the passed bundle. The embedded environment has some special
+     * requirements for loading resources so the implementation will abstract those.
+     * As a single bundle is passed the result will contain at most one element, or be null if
+     * none is found.
+     */
+    public <T> Enumeration<URL> getResourcesFromBundle(String type, Bundle b) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b0d30700/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFramework.java
----------------------------------------------------------------------
diff --git a/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFramework.java b/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFramework.java
index d12cb80..0323112 100644
--- a/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFramework.java
+++ b/utils/rt-felix/src/main/java/org/apache/brooklyn/rt/felix/EmbeddedFramework.java
@@ -15,6 +15,10 @@
  */
 package org.apache.brooklyn.rt.felix;
 
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
 import org.apache.brooklyn.util.osgi.SystemFramework;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.launch.Framework;
@@ -51,4 +55,22 @@ public class EmbeddedFramework implements SystemFramework {
         return clazz;
     }
 
+    @Override
+    public <T> URL getResourceFromBundle(String type, Bundle b) {
+        if (EmbeddedFelixFramework.isExtensionBundle(b)) {
+            return getClass().getClassLoader().getResource(type);
+        } else {
+            return b.getResource(type);
+        }
+    }
+
+    @Override
+    public <T> Enumeration<URL> getResourcesFromBundle(String type, Bundle b) throws IOException {
+        if (EmbeddedFelixFramework.isExtensionBundle(b)) {
+            return getClass().getClassLoader().getResources(type);
+        } else {
+            return b.getResources(type);
+        }
+    }
+
 }


[2/2] brooklyn-server git commit: This closes #242

Posted by al...@apache.org.
This closes #242


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/5e732612
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/5e732612
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/5e732612

Branch: refs/heads/master
Commit: 5e732612c30cee4cb2cc902c679f95e936731853
Parents: 0d4fa31 b0d3070
Author: Aled Sage <al...@gmail.com>
Authored: Tue Jul 12 15:04:03 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Jul 12 15:04:03 2016 +0100

----------------------------------------------------------------------
 .../brooklyn/camp/brooklyn/RebindOsgiTest.java  |   1 +
 .../JavaBrooklynClassLoadingContext.java        |  16 +-
 .../brooklyn/util/core/ClassLoaderUtils.java    | 194 ++++++++++++------
 .../brooklyn/util/core/LoaderDispatcher.java    | 161 +++++++++++++++
 .../util/core/osgi/ContainerFramework.java      |  14 ++
 .../util/core/ClassLoaderUtilsTest.java         | 201 ++++++++++++++++---
 .../brooklyn/util/osgi/SystemFramework.java     |  18 ++
 .../brooklyn/rt/felix/EmbeddedFramework.java    |  22 ++
 8 files changed, 521 insertions(+), 106 deletions(-)
----------------------------------------------------------------------