diff --git a/core/src/main/java/org/jclouds/osgi/MetadataBundleListener.java b/core/src/main/java/org/jclouds/osgi/MetadataBundleListener.java index 07cbf585c4..2c79713d5c 100644 --- a/core/src/main/java/org/jclouds/osgi/MetadataBundleListener.java +++ b/core/src/main/java/org/jclouds/osgi/MetadataBundleListener.java @@ -18,6 +18,11 @@ */ package org.jclouds.osgi; +import com.google.common.base.Charsets; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.io.Closeables; import org.jclouds.apis.ApiMetadata; import org.jclouds.apis.ApiRegistry; import org.jclouds.providers.ProviderMetadata; @@ -31,8 +36,8 @@ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; -import java.util.HashMap; -import java.util.Map; +import java.util.Collection; +import java.util.List; /** * A {@link BundleListener} that listens for {@link BundleEvent} and searches for {@link org.jclouds.providers.ProviderMetadata} and {@link org.jclouds.apis.ApiMetadata} in newly @@ -41,168 +46,183 @@ import java.util.Map; */ public class MetadataBundleListener implements BundleListener { - private Map providerMetadataMap = new HashMap(); - private Map apiMetadataMap = new HashMap(); + private Multimap providerMetadataMap = ArrayListMultimap.create(); + private Multimap apiMetadataMap = ArrayListMultimap.create(); - public void start(BundleContext bundleContext) { - bundleContext.addBundleListener(this); - for (Bundle bundle : bundleContext.getBundles()) { - if (bundle.getState() == Bundle.ACTIVE) { - ProviderMetadata providerMetadata = getProviderMetadata(bundle); - ApiMetadata apiMetadata = getApiMetadata(bundle); + public void start(BundleContext bundleContext) { + bundleContext.addBundleListener(this); + for (Bundle bundle : bundleContext.getBundles()) { + if (bundle.getState() == Bundle.ACTIVE) { + List providerMetadataList = getProviderMetadata(bundle); + List apiMetadataList = getApiMetadata(bundle); - if (providerMetadata != null) { - ProviderRegistry.registerProvider(providerMetadata); - providerMetadataMap.put(bundle.getBundleId(), providerMetadata); - } - if (apiMetadata != null) { - ApiRegistry.registerApi(apiMetadata); - apiMetadataMap.put(bundle.getBundleId(), apiMetadata); - } + for (ProviderMetadata providerMetadata : providerMetadataList) { + if (providerMetadata != null) { + ProviderRegistry.registerProvider(providerMetadata); + providerMetadataMap.put(bundle.getBundleId(), providerMetadata); + } + } + + for (ApiMetadata apiMetadata : apiMetadataList) { + if (apiMetadata != null) { + ApiRegistry.registerApi(apiMetadata); + apiMetadataMap.put(bundle.getBundleId(), apiMetadata); + } + } + } } - } - } + } - public void stop(BundleContext bundleContext) { - providerMetadataMap.clear(); - apiMetadataMap.clear(); - } + public void stop(BundleContext bundleContext) { + providerMetadataMap.clear(); + apiMetadataMap.clear(); + } - @Override - public void bundleChanged(BundleEvent event) { - ProviderMetadata providerMetadata; - ApiMetadata apiMetadata; - switch (event.getType()) { - case BundleEvent.STARTED: - providerMetadata = getProviderMetadata(event.getBundle()); - apiMetadata = getApiMetadata(event.getBundle()); - if (providerMetadata != null) { - ProviderRegistry.registerProvider(providerMetadata); - providerMetadataMap.put(event.getBundle().getBundleId(), providerMetadata); - } - if (apiMetadata != null) { - ApiRegistry.registerApi(apiMetadata); - apiMetadataMap.put(event.getBundle().getBundleId(), apiMetadata); - } - break; - case BundleEvent.STOPPING: - case BundleEvent.STOPPED: - providerMetadata = providerMetadataMap.get(event.getBundle().getBundleId()); - apiMetadata = apiMetadataMap.get(event.getBundle().getBundleId()); - if (providerMetadata != null) { - ProviderRegistry.unregisterProvider(providerMetadata); - } - if (apiMetadata != null) { - ApiRegistry.unRegisterApi(apiMetadata); - } - break; - } - } + @Override + public void bundleChanged(BundleEvent event) { + Collection providerMetadataList = null; + Collection apiMetadataList = null; + switch (event.getType()) { + case BundleEvent.STARTED: + providerMetadataList = getProviderMetadata(event.getBundle()); + apiMetadataList = getApiMetadata(event.getBundle()); + for (ProviderMetadata providerMetadata : providerMetadataList) { + if (providerMetadata != null) { + ProviderRegistry.registerProvider(providerMetadata); + providerMetadataMap.put(event.getBundle().getBundleId(), providerMetadata); + } + } + + for (ApiMetadata apiMetadata : apiMetadataList) { + if (apiMetadata != null) { + ApiRegistry.registerApi(apiMetadata); + apiMetadataMap.put(event.getBundle().getBundleId(), apiMetadata); + } + } + break; + case BundleEvent.STOPPING: + case BundleEvent.STOPPED: + providerMetadataList = providerMetadataMap.get(event.getBundle().getBundleId()); + apiMetadataList = apiMetadataMap.get(event.getBundle().getBundleId()); + + if (providerMetadataList != null) { + for (ProviderMetadata providerMetadata : providerMetadataList) { + ProviderRegistry.unregisterProvider(providerMetadata); + } + } + if (apiMetadataList != null) { + for (ApiMetadata apiMetadata : apiMetadataList) { + ApiRegistry.unRegisterApi(apiMetadata); + } + } + break; + } + } + + /** + * Creates an instance of {@link ProviderMetadata} from the {@link Bundle}. + * + * @param bundle + * @return + */ + public List getProviderMetadata(Bundle bundle) { + List metadataList = Lists.newArrayList(); + String classNames = getProviderMetadataClassNames(bundle); + if (classNames != null && !classNames.isEmpty()) { + for (String className : classNames.split("\n")) { + try { + Class providerMetadataClass = bundle.loadClass(className); + //Classes loaded by other class loaders are not assignable. + if (ProviderMetadata.class.isAssignableFrom(providerMetadataClass)) { + ProviderMetadata metadata = providerMetadataClass.newInstance(); + metadataList.add(metadata); + } + } catch (ClassNotFoundException e) { + // ignore + } catch (InstantiationException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } + } + } + return metadataList; + } + + /** + * Creates an instance of {@link ApiMetadata} from the {@link Bundle}. + * + * @param bundle + * @return + */ + public List getApiMetadata(Bundle bundle) { + List metadataList = Lists.newArrayList(); + String classNames = getApiMetadataClassNames(bundle); + if (classNames != null && !classNames.isEmpty()) { + for (String className : classNames.split("\n")) { + try { + Class apiMetadataClass = bundle.loadClass(className); + //Classes loaded by other class loaders are not assignable. + if (ApiMetadata.class.isAssignableFrom(apiMetadataClass)) { + ApiMetadata metadata = apiMetadataClass.newInstance(); + metadataList.add(metadata); + } + } catch (ClassNotFoundException e) { + // ignore + } catch (InstantiationException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } + } + } + return metadataList; + } + + + public String getMetadataClassNames(Bundle bundle, String pathToMetadata) { + URL resource = bundle.getEntry(pathToMetadata); + InputStream is = null; + InputStreamReader reader = null; + BufferedReader bufferedReader = null; + StringBuilder sb = new StringBuilder(); - /** - * Creates an instance of {@link ProviderMetadata} from the {@link Bundle}. - * - * @param bundle - * @return - */ - public ProviderMetadata getProviderMetadata(Bundle bundle) { - ProviderMetadata metadata = null; - String className = getProviderMetadataClassName(bundle); - if (className != null && !className.isEmpty()) { try { - Class providerMetadataClass = bundle.loadClass(className); - metadata = providerMetadataClass.newInstance(); - } catch (ClassNotFoundException e) { - // ignore - } catch (InstantiationException e) { - // ignore - } catch (IllegalAccessException e) { - // ignore - } - } - return metadata; - } - - /** - * Creates an instance of {@link ApiMetadata} from the {@link Bundle}. - * - * @param bundle - * @return - */ - public ApiMetadata getApiMetadata(Bundle bundle) { - ApiMetadata metadata = null; - String className = getApiMetadataClassName(bundle); - if (className != null && !className.isEmpty()) { - try { - Class apiMetadataClass = bundle.loadClass(className); - metadata = apiMetadataClass.newInstance(); - } catch (ClassNotFoundException e) { - // ignore - } catch (InstantiationException e) { - // ignore - } catch (IllegalAccessException e) { - // ignore - } - } - return metadata; - } - - - public String getMetadataClassName(Bundle bundle, String pathToMetadata) { - URL resource = bundle.getEntry(pathToMetadata); - InputStream is = null; - InputStreamReader reader = null; - BufferedReader bufferedReader = null; - StringBuilder sb = new StringBuilder(); - - try { - is = resource.openStream(); - reader = new InputStreamReader(is, "UTF-8"); - bufferedReader = new BufferedReader(reader); - String line; - while ((line = bufferedReader.readLine()) != null) { - sb.append(line).append("\n"); - } - } catch (Throwable e) { - } finally { - try { - if (reader != null) - reader.close(); - } catch (Throwable e) { - } - try { - if (bufferedReader != null) - bufferedReader.close(); - } catch (Throwable e) { - } - try { - is.close(); + is = resource.openStream(); + reader = new InputStreamReader(is, Charsets.UTF_8); + bufferedReader = new BufferedReader(reader); + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line).append("\n"); + } } catch (Throwable e) { + } finally { + Closeables.closeQuietly(reader); + Closeables.closeQuietly(bufferedReader); + Closeables.closeQuietly(is); } + return sb.toString().trim(); + } - } - return sb.toString().trim(); - } + /** + * Retrieves the {@link ProviderMetadata} class name for the bundle if it exists. + * + * @param bundle + * @return + */ + public String getProviderMetadataClassNames(Bundle bundle) { + return getMetadataClassNames(bundle, "/META-INF/services/org.jclouds.providers.ProviderMetadata"); + } - /** - * Retrieves the {@link ProviderMetadata} class name for the bundle if it exists. - * - * @param bundle - * @return - */ - public String getProviderMetadataClassName(Bundle bundle) { - return getMetadataClassName(bundle, "/META-INF/services/org.jclouds.providers.ProviderMetadata"); - } - - /** - * Retrieves the {@link ProviderMetadata} class name for the bundle if it exists. - * - * @param bundle - * @return - */ - public String getApiMetadataClassName(Bundle bundle) { - return getMetadataClassName(bundle, "/META-INF/services/org.jclouds.apis.ApiMetadata"); - } + /** + * Retrieves the {@link ProviderMetadata} class name for the bundle if it exists. + * + * @param bundle + * @return + */ + public String getApiMetadataClassNames(Bundle bundle) { + return getMetadataClassNames(bundle, "/META-INF/services/org.jclouds.apis.ApiMetadata"); + } } \ No newline at end of file diff --git a/core/src/test/java/org/jclouds/osgi/MetadataBundleListenerTest.java b/core/src/test/java/org/jclouds/osgi/MetadataBundleListenerTest.java new file mode 100644 index 0000000000..697877491e --- /dev/null +++ b/core/src/test/java/org/jclouds/osgi/MetadataBundleListenerTest.java @@ -0,0 +1,147 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.osgi; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.apis.JcloudsTestBlobStoreApiMetadata; +import org.jclouds.apis.JcloudsTestComputeApiMetadata; +import org.jclouds.apis.JcloudsTestYetAnotherComputeApiMetadata; +import org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata; +import org.jclouds.providers.JcloudsTestComputeProviderMetadata; +import org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata; +import org.jclouds.providers.ProviderMetadata; +import org.osgi.framework.Bundle; +import org.testng.annotations.Test; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertNotNull; + + +public class MetadataBundleListenerTest { + + @Test + public void testSanity() throws MalformedURLException, ClassNotFoundException { + //We are checking here that the class loader we create and use in this test series is indeed different and isolated from our tests classloader. + ClassLoader loader = createIsolatedClassLoader(); + assertFalse(ProviderMetadata.class.isAssignableFrom(loader.loadClass("org.jclouds.providers.JcloudsTestComputeProviderMetadata"))); + } + + + @Test + public void testGetProviderMetadata() throws Exception { + MetadataBundleListener listener = new MetadataBundleListener(); + Bundle bundle = createMock(Bundle.class); + expect(bundle.getEntry("/META-INF/services/org.jclouds.providers.ProviderMetadata")).andReturn(getClass().getResource("/META-INF/services/org.jclouds.providers.ProviderMetadata")).anyTimes(); + expect(bundle.loadClass("org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata")).andReturn(JcloudsTestBlobStoreProviderMetadata.class).anyTimes(); + expect(bundle.loadClass("org.jclouds.providers.JcloudsTestComputeProviderMetadata")).andReturn(JcloudsTestComputeProviderMetadata.class).anyTimes(); + expect(bundle.loadClass("org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata")).andReturn(JcloudsTestYetAnotherComputeProviderMetadata.class).anyTimes(); + + replay(bundle); + List providerMetadataList = listener.getProviderMetadata(bundle); + assertNotNull(providerMetadataList); + assertEquals(3, providerMetadataList.size()); + assertTrue(providerMetadataList.contains(new JcloudsTestBlobStoreProviderMetadata())); + assertTrue(providerMetadataList.contains(new JcloudsTestComputeProviderMetadata())); + assertTrue(providerMetadataList.contains(new JcloudsTestYetAnotherComputeProviderMetadata())); + verify(bundle); + } + + @Test + public void testGetProviderMetadataFromMultipleClassLoaders() throws Exception { + ClassLoader isolatedClassLoader = createIsolatedClassLoader(); + MetadataBundleListener listener = new MetadataBundleListener(); + Bundle bundle = createMock(Bundle.class); + expect(bundle.getEntry("/META-INF/services/org.jclouds.providers.ProviderMetadata")).andReturn(getClass().getResource("/META-INF/services/org.jclouds.providers.ProviderMetadata")).anyTimes(); + expect(bundle.loadClass("org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata")).andReturn(isolatedClassLoader.loadClass(JcloudsTestBlobStoreProviderMetadata.class.getName())).anyTimes(); + expect(bundle.loadClass("org.jclouds.providers.JcloudsTestComputeProviderMetadata")).andReturn(JcloudsTestComputeProviderMetadata.class).anyTimes(); + expect(bundle.loadClass("org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata")).andReturn(JcloudsTestYetAnotherComputeProviderMetadata.class).anyTimes(); + + replay(bundle); + List providerMetadataList = listener.getProviderMetadata(bundle); + assertNotNull(providerMetadataList); + assertEquals(2, providerMetadataList.size()); + assertFalse(providerMetadataList.contains(new JcloudsTestBlobStoreProviderMetadata())); + assertTrue(providerMetadataList.contains(new JcloudsTestComputeProviderMetadata())); + assertTrue(providerMetadataList.contains(new JcloudsTestYetAnotherComputeProviderMetadata())); + verify(bundle); + } + + @Test + public void testGetApiMetadata() throws Exception { + MetadataBundleListener listener = new MetadataBundleListener(); + Bundle bundle = createMock(Bundle.class); + expect(bundle.getEntry("/META-INF/services/org.jclouds.apis.ApiMetadata")).andReturn(getClass().getResource("/META-INF/services/org.jclouds.apis.ApiMetadata")).anyTimes(); + expect(bundle.loadClass("org.jclouds.apis.JcloudsTestBlobStoreApiMetadata")).andReturn(JcloudsTestBlobStoreApiMetadata.class).anyTimes(); + expect(bundle.loadClass("org.jclouds.apis.JcloudsTestComputeApiMetadata")).andReturn(JcloudsTestComputeApiMetadata.class).anyTimes(); + expect(bundle.loadClass("org.jclouds.apis.JcloudsTestYetAnotherComputeApiMetadata")).andReturn(JcloudsTestYetAnotherComputeApiMetadata.class).anyTimes(); + + replay(bundle); + List apiMetadataList = listener.getApiMetadata(bundle); + assertNotNull(apiMetadataList); + assertEquals(3, apiMetadataList.size()); + assertTrue(apiMetadataList.contains(new JcloudsTestBlobStoreApiMetadata())); + assertTrue(apiMetadataList.contains(new JcloudsTestComputeApiMetadata())); + assertTrue(apiMetadataList.contains(new JcloudsTestYetAnotherComputeApiMetadata())); + verify(bundle); + } + + @Test + public void testGetApiMetadataFromMultipleClassLoaders() throws Exception { + ClassLoader isolatedClassLoader = createIsolatedClassLoader(); + MetadataBundleListener listener = new MetadataBundleListener(); + Bundle bundle = createMock(Bundle.class); + expect(bundle.getEntry("/META-INF/services/org.jclouds.apis.ApiMetadata")).andReturn(getClass().getResource("/META-INF/services/org.jclouds.apis.ApiMetadata")).anyTimes(); + expect(bundle.loadClass("org.jclouds.apis.JcloudsTestBlobStoreApiMetadata")).andReturn(isolatedClassLoader.loadClass(JcloudsTestBlobStoreApiMetadata.class.getName())).anyTimes(); + expect(bundle.loadClass("org.jclouds.apis.JcloudsTestComputeApiMetadata")).andReturn(JcloudsTestComputeApiMetadata.class).anyTimes(); + expect(bundle.loadClass("org.jclouds.apis.JcloudsTestYetAnotherComputeApiMetadata")).andReturn(JcloudsTestYetAnotherComputeApiMetadata.class).anyTimes(); + + replay(bundle); + List apiMetadataList = listener.getApiMetadata(bundle); + assertNotNull(apiMetadataList); + assertEquals(2, apiMetadataList.size()); + assertFalse(apiMetadataList.contains(new JcloudsTestBlobStoreApiMetadata())); + assertTrue(apiMetadataList.contains(new JcloudsTestComputeApiMetadata())); + assertTrue(apiMetadataList.contains(new JcloudsTestYetAnotherComputeApiMetadata())); + verify(bundle); + } + + + /** + * Creates a different {@link ClassLoader}. + * + * @return + */ + private ClassLoader createIsolatedClassLoader() throws MalformedURLException { + URLClassLoader testClassLoader = (URLClassLoader) getClass().getClassLoader(); + URL[] urls = testClassLoader.getURLs(); + URLClassLoader loader = new URLClassLoader(urls, null); + return loader; + } +}