Merge pull request #1053 from jclouds/jclouds-osgi-listener

Added listener interfaces for Providers and Apis.
This commit is contained in:
Adrian Cole 2012-12-08 17:41:55 -08:00
commit 5d62acc742
12 changed files with 673 additions and 216 deletions

View File

@ -116,6 +116,12 @@
<version>4.2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<version>4.2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@ -29,6 +29,7 @@ import org.jclouds.View;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
@ -69,7 +70,9 @@ public class Apis {
* @return all available apis
*/
public static Iterable<ApiMetadata> all() {
return Iterables.concat(fromServiceLoader(), ApiRegistry.fromRegistry());
return ImmutableSet.<ApiMetadata>builder()
.addAll(fromServiceLoader())
.addAll(ApiRegistry.fromRegistry()).build();
}
/**

View File

@ -22,54 +22,102 @@ import org.jclouds.apis.ApiRegistry;
import org.jclouds.providers.ProviderRegistry;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator {
MetadataBundleListener bundleListener = new MetadataBundleListener();
private ServiceTracker providerListenerTracker = null;
private ServiceTracker apiListenerTracker = null;
private MetadataBundleListener bundleListener = new MetadataBundleListener();
/**
* Called when this bundle is started so the Framework can perform the
* bundle-specific activities necessary to start this bundle. This method
* can be used to register services or to allocate any resources that this
* bundle needs.
* <p/>
* <p/>
* This method must complete and return to its caller in a timely manner.
*
* @param context The execution context of the bundle being started.
* @throws Exception If this method throws an exception, this
* bundle is marked as stopped and the Framework will remove this
* bundle's listeners, unregister all services registered by this
* bundle, and release all services used by this bundle.
*/
@Override
public void start(BundleContext context) throws Exception {
bundleListener.start(context);
context.addBundleListener(bundleListener);
}
/**
* Called when this bundle is started so the Framework can perform the
* bundle-specific activities necessary to start this bundle. This method
* can be used to register services or to allocate any resources that this
* bundle needs.
* <p/>
* <p/>
* This method must complete and return to its caller in a timely manner.
*
* @param context The execution context of the bundle being started.
* @throws Exception If this method throws an exception, this
* bundle is marked as stopped and the Framework will remove this
* bundle's listeners, unregister all services registered by this
* bundle, and release all services used by this bundle.
*/
@Override
public void start(BundleContext context) throws Exception {
bundleListener.start(context);
providerListenerTracker = new ServiceTracker(context, ProviderListener.class.getName(), null) {
@Override
public Object addingService(ServiceReference reference) {
Object obj = super.addingService(reference);
if (ProviderListener.class.isAssignableFrom(obj.getClass())) {
bundleListener.addProviderListener((ProviderListener) obj);
}
return obj;
}
/**
* Called when this bundle is stopped so the Framework can perform the
* bundle-specific activities necessary to stop the bundle. In general, this
* method should undo the work that the <code>BundleActivator.start</code>
* method started. There should be no active threads that were started by
* this bundle when this bundle returns. A stopped bundle must not call any
* Framework objects.
* <p/>
* <p/>
* This method must complete and return to its caller in a timely manner.
*
* @param context The execution context of the bundle being stopped.
* @throws Exception If this method throws an exception, the
* bundle is still marked as stopped, and the Framework will remove
* the bundle's listeners, unregister all services registered by the
* bundle, and release all services used by the bundle.
*/
@Override
public void stop(BundleContext context) throws Exception {
bundleListener.stop(context);
context.removeBundleListener(bundleListener);
ProviderRegistry.clear();
ApiRegistry.clear();
}
@Override
public void removedService(ServiceReference reference, Object service) {
if (ProviderListener.class.isAssignableFrom(service.getClass())) {
bundleListener.removeProviderListener((ProviderListener) service);
}
super.removedService(reference, service);
}
};
apiListenerTracker = new ServiceTracker(context, ApiListener.class.getName(), null) {
@Override
public Object addingService(ServiceReference reference) {
Object obj = super.addingService(reference);
if (ApiListener.class.isAssignableFrom(obj.getClass())) {
bundleListener.addApiListenerListener((ApiListener) obj);
}
return obj;
}
@Override
public void removedService(ServiceReference reference, Object service) {
if (ApiListener.class.isAssignableFrom(service.getClass())) {
bundleListener.removeApiListenerListener((ApiListener) service);
}
super.removedService(reference, service);
}
};
providerListenerTracker.open();
apiListenerTracker.open();
}
/**
* Called when this bundle is stopped so the Framework can perform the
* bundle-specific activities necessary to stop the bundle. In general, this
* method should undo the work that the <code>BundleActivator.start</code>
* method started. There should be no active threads that were started by
* this bundle when this bundle returns. A stopped bundle must not call any
* Framework objects.
* <p/>
* <p/>
* This method must complete and return to its caller in a timely manner.
*
* @param context The execution context of the bundle being stopped.
* @throws Exception If this method throws an exception, the
* bundle is still marked as stopped, and the Framework will remove
* the bundle's listeners, unregister all services registered by the
* bundle, and release all services used by the bundle.
*/
@Override
public void stop(BundleContext context) throws Exception {
bundleListener.stop(context);
ProviderRegistry.clear();
ApiRegistry.clear();
if (apiListenerTracker != null) {
apiListenerTracker.close();
}
if (providerListenerTracker != null) {
providerListenerTracker.close();
}
}
}

View File

@ -0,0 +1,46 @@
/**
* 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;
/**
* A listener interface for {@link org.jclouds.apis.ApiMetadata}.
* In OSGi a api can be added or removed dynamically.
* OSGi services using this interface will receive a notification whenever this happens.
*/
public interface ApiListener {
/**
* Method to be called when an api gets added.
*
* @param api The api that was added.
* @param <A> The {@link org.jclouds.apis.ApiMetadata}.
*/
<A extends ApiMetadata> void added(A api);
/**
* Method to be called when an api gets removed.
*
* @param api The api that was added.
* @param <A> The {@link ApiMetadata}.
*/
<A extends ApiMetadata> void removed(A api);
}

View File

@ -0,0 +1,143 @@
/**
* 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 static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.jclouds.util.Strings2;
import org.osgi.framework.Bundle;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
/**
* Utility functions helpful in working with {@link Bundle bundles}.
*
* @author Adrian Cole
*/
public final class Bundles {
private Bundles() {
}
/**
* instantiates the supplied classnames using the bundle classloader, and
* casts to the supplied type. Any errors are silently ignored.
*
* @return instances that could be instantiated without error.
*/
public static <T> ImmutableSet<T> instantiateAvailableClasses(Bundle bundle, Iterable<String> classNames, Class<T> type) {
checkNotNull(bundle, "bundle");
checkNotNull(classNames, "classNames");
checkNotNull(type, "type");
return FluentIterable.from(classNames)
.transform(loadClassIfAssignableFrom(bundle, type))
.filter(Predicates.notNull())
.transform(instantiateIfPossible(type))
.filter(Predicates.notNull())
.toImmutableSet();
}
/**
* A function that loads classes from the bundle, or returns null if the
* class isn't found or assignable by the input parameter
*
* @param bundle
* where to find classes
* @param clazz
* type classes must be assignable from
*/
private static Function<String, Class<?>> loadClassIfAssignableFrom(final Bundle bundle, final Class<?> clazz) {
checkNotNull(bundle, "bundle");
checkNotNull(clazz, "clazz");
return new Function<String, Class<?>>() {
@Override
public Class<?> apply(String in) {
checkNotNull(in, "classname");
try {
Class<?> thing = bundle.loadClass(in);
// Classes loaded by other class loaders are not assignable.
if (clazz.isAssignableFrom(thing))
return thing.asSubclass(clazz);
} catch (ClassNotFoundException e) {
}
return null;
}
};
}
/**
* A function that instantiates classes or returns null, if it encounters any
* problems.
*
* @param clazz
* superclass to cast as
*/
private static <T> Function<Class<?>, T> instantiateIfPossible(final Class<T> clazz) {
return new Function<Class<?>, T>() {
@Override
public T apply(Class<?> in) {
checkNotNull(in, "input class");
try {
return clazz.cast(in.newInstance());
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
return null;
}
};
}
/**
* Reads the resource from a {@link Bundle}.
*
* @param resourcePath
* The path to the resource.
* @param bundle
* The bundle to read from.
* @return strings delimited by newline in the stream or empty set, on any
* exception.
*/
public static ImmutableSet<String> stringsForResorceInBundle(String resourcePath, Bundle bundle) {
checkNotNull(resourcePath, "resourcePath");
checkNotNull(bundle, "bundle");
URL resource = bundle.getEntry(resourcePath);
if (resource == null)
return ImmutableSet.of();
try {
return ImmutableSet.copyOf(splitOrEmptyAndClose(resource.openStream()));
} catch (IOException e) {
return ImmutableSet.of();
} catch (RuntimeException ex) {
return ImmutableSet.of();
}
}
private static Iterable<String> splitOrEmptyAndClose(InputStream in) throws IOException {
return Splitter.on('\n').omitEmptyStrings().split(Strings2.toStringAndClose(in));
}
}

View File

@ -18,11 +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 static org.jclouds.osgi.Bundles.instantiateAvailableClasses;
import static org.jclouds.osgi.Bundles.stringsForResorceInBundle;
import java.util.List;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.apis.ApiRegistry;
import org.jclouds.providers.ProviderMetadata;
@ -32,91 +32,115 @@ import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
/**
* A {@link BundleListener} that listens for {@link BundleEvent} and searches for {@link org.jclouds.providers.ProviderMetadata} and {@link org.jclouds.apis.ApiMetadata} in newly
* installed Bundles. This is used as a workaround for OSGi environments where the ServiceLoader cannot cross bundle
* boundaries.
*
* @author iocanel
*/
public class MetadataBundleListener implements BundleListener {
private Multimap<Long, ProviderMetadata> providerMetadataMap = ArrayListMultimap.create();
private Multimap<Long, ApiMetadata> apiMetadataMap = ArrayListMultimap.create();
private final Multimap<Long, ProviderMetadata> providerMetadataMap = ArrayListMultimap.create();
private final Multimap<Long, ApiMetadata> apiMetadataMap = ArrayListMultimap.create();
private final List<ProviderListener> providerListeners = Lists.newArrayList();
private final List<ApiListener> apiListeners = Lists.newArrayList();
public void start(BundleContext bundleContext) {
/**
* Starts the listener.
* Checks the bundles that are already active and registers {@link ProviderMetadata} and {@link ApiMetadata} found.
* Registers the itself as a {@link BundleListener}.
* @param bundleContext
*/
public synchronized void start(BundleContext bundleContext) {
bundleContext.addBundleListener(this);
for (Bundle bundle : bundleContext.getBundles()) {
if (bundle.getState() == Bundle.ACTIVE) {
List<ProviderMetadata> providerMetadataList = getProviderMetadata(bundle);
List<ApiMetadata> apiMetadataList = getApiMetadata(bundle);
addBundle(bundle);
}
}
bundleContext.addBundleListener(this);
}
for (ProviderMetadata providerMetadata : providerMetadataList) {
if (providerMetadata != null) {
ProviderRegistry.registerProvider(providerMetadata);
providerMetadataMap.put(bundle.getBundleId(), providerMetadata);
}
/**
* Stops the listener.
* Removes itself from the {@link BundleListener}s.
* Clears metadata maps and listeners lists.
* @param bundleContext
*/
public void stop(BundleContext bundleContext) {
bundleContext.removeBundleListener(this);
providerMetadataMap.clear();
apiMetadataMap.clear();
apiListeners.clear();
providerListeners.clear();
}
@Override
public synchronized void bundleChanged(BundleEvent event) {
switch (event.getType()) {
case BundleEvent.STARTED:
addBundle(event.getBundle());
break;
case BundleEvent.STOPPING:
case BundleEvent.STOPPED:
removeBundle(event.getBundle());
break;
}
}
/**
* Searches for {@link ProviderMetadata} and {@link ApiMetadata} inside the {@link Bundle}.
* If metadata are found they are registered in the {@link ProviderRegistry} and {@link ApiRegistry}.
* Also the {@link ProviderListener} and {@link ApiListener} are notified.
* @param bundle
*/
private synchronized void addBundle(Bundle bundle) {
for (ProviderMetadata providerMetadata : listProviderMetadata(bundle)) {
if (providerMetadata != null) {
ProviderRegistry.registerProvider(providerMetadata);
providerMetadataMap.put(bundle.getBundleId(), providerMetadata);
for (ProviderListener listener : providerListeners) {
listener.added(providerMetadata);
}
}
}
for (ApiMetadata apiMetadata : apiMetadataList) {
if (apiMetadata != null) {
ApiRegistry.registerApi(apiMetadata);
apiMetadataMap.put(bundle.getBundleId(), apiMetadata);
}
for (ApiMetadata apiMetadata : listApiMetadata(bundle)) {
if (apiMetadata != null) {
ApiRegistry.registerApi(apiMetadata);
apiMetadataMap.put(bundle.getBundleId(), apiMetadata);
for (ApiListener listener : apiListeners) {
listener.added(apiMetadata);
}
}
}
}
public void stop(BundleContext bundleContext) {
providerMetadataMap.clear();
apiMetadataMap.clear();
}
@Override
public void bundleChanged(BundleEvent event) {
Collection<ProviderMetadata> providerMetadataList = null;
Collection<ApiMetadata> 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;
/**
* Searches for {@link ProviderMetadata} and {@link ApiMetadata} registered under the {@link Bundle} id.
* If metadata are found they are removed the {@link ProviderRegistry} and {@link ApiRegistry}.
* Also the {@link ProviderListener} and {@link ApiListener} are notified.
* @param bundle
*/
private synchronized void removeBundle(Bundle bundle) {
for (ProviderMetadata providerMetadata : providerMetadataMap.removeAll(bundle.getBundleId())) {
ProviderRegistry.unregisterProvider(providerMetadata);
for (ProviderListener listener : providerListeners) {
listener.removed(providerMetadata);
}
}
for (ApiMetadata apiMetadata : apiMetadataMap.removeAll(bundle.getBundleId())) {
ApiRegistry.unRegisterApi(apiMetadata);
for (ApiListener listener : apiListeners) {
listener.removed(apiMetadata);
}
}
}
@ -126,28 +150,9 @@ public class MetadataBundleListener implements BundleListener {
* @param bundle
* @return
*/
public List<ProviderMetadata> getProviderMetadata(Bundle bundle) {
List<ProviderMetadata> metadataList = Lists.newArrayList();
String classNames = getProviderMetadataClassNames(bundle);
if (classNames != null && !classNames.isEmpty()) {
for (String className : classNames.split("\n")) {
try {
Class<? extends ProviderMetadata> 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;
public Iterable<ProviderMetadata> listProviderMetadata(Bundle bundle) {
Iterable<String> classNames = stringsForResorceInBundle("/META-INF/services/org.jclouds.providers.ProviderMetadata", bundle);
return instantiateAvailableClasses(bundle, classNames, ProviderMetadata.class);
}
/**
@ -156,73 +161,51 @@ public class MetadataBundleListener implements BundleListener {
* @param bundle
* @return
*/
public List<ApiMetadata> getApiMetadata(Bundle bundle) {
List<ApiMetadata> metadataList = Lists.newArrayList();
String classNames = getApiMetadataClassNames(bundle);
if (classNames != null && !classNames.isEmpty()) {
for (String className : classNames.split("\n")) {
try {
Class<? extends ApiMetadata> 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();
try {
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();
public Iterable<ApiMetadata> listApiMetadata(Bundle bundle) {
Iterable<String> classNames = stringsForResorceInBundle("/META-INF/services/org.jclouds.apis.ApiMetadata", bundle);
return instantiateAvailableClasses(bundle, classNames, ApiMetadata.class);
}
/**
* Retrieves the {@link ProviderMetadata} class name for the bundle if it exists.
* Adds a {@link ProviderListener} and notifies it of existing {@link ProviderMetadata}.
*
* @param bundle
* @return
* @param listener The listener.
*/
public String getProviderMetadataClassNames(Bundle bundle) {
return getMetadataClassNames(bundle, "/META-INF/services/org.jclouds.providers.ProviderMetadata");
public synchronized void addProviderListener(ProviderListener listener) {
providerListeners.add(listener);
for (ProviderMetadata metadata : providerMetadataMap.values()) {
listener.added(metadata);
}
}
/**
* Retrieves the {@link ProviderMetadata} class name for the bundle if it exists.
* Removes the {@link ProviderListener}
*
* @param bundle
* @return
* @param listener The listener
*/
public String getApiMetadataClassNames(Bundle bundle) {
return getMetadataClassNames(bundle, "/META-INF/services/org.jclouds.apis.ApiMetadata");
public synchronized void removeProviderListener(ProviderListener listener) {
providerListeners.remove(listener);
}
/**
* Adds a {@link ApiListener} and notifies it of existing {@link ApiMetadata}.
*
* @param listener
*/
public synchronized void addApiListenerListener(ApiListener listener) {
apiListeners.add(listener);
for (ApiMetadata metadata : apiMetadataMap.values()) {
listener.added(metadata);
}
}
/**
* Removes the {@link ApiListener}
*
* @param listener
*/
public synchronized void removeApiListenerListener(ApiListener listener) {
apiListeners.remove(listener);
}
}

View File

@ -0,0 +1,47 @@
/**
* 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.providers.ProviderMetadata;
/**
* A listener interface for {@link ProviderMetadata}.
* In OSGi a provider can be added or removed dynamically.
* OSGi services using this interface will receive a notification whenever this happens.
*/
public interface ProviderListener {
/**
* Method to be called when a Provider gets added.
*
* @param provider The provider that was added.
* @param <P> The {@link ProviderMetadata}.
*/
<P extends ProviderMetadata> void added(P provider);
/**
* Method to be called when a Provider gets removed.
*
* @param provider The provider that was added.
* @param <P> The {@link ProviderMetadata}.
*/
<P extends ProviderMetadata> void removed(P provider);
}

View File

@ -27,21 +27,21 @@ import com.google.common.collect.Sets;
*/
public class ProviderRegistry {
private static final Set<ProviderMetadata> providers = Sets.newHashSet();
private static final Set<ProviderMetadata> providers = Sets.newHashSet();
public static void registerProvider(ProviderMetadata provider) {
providers.add(provider);
}
public static void registerProvider(ProviderMetadata provider) {
providers.add(provider);
}
public static void unregisterProvider(ProviderMetadata provider) {
providers.remove(provider);
}
public static void unregisterProvider(ProviderMetadata provider) {
providers.remove(provider);
}
public static Iterable<ProviderMetadata> fromRegistry() {
return Iterable.class.cast(providers);
}
public static Iterable<ProviderMetadata> fromRegistry() {
return Iterable.class.cast(providers);
}
public static void clear() {
providers.clear();
}
public static void clear() {
providers.clear();
}
}

View File

@ -24,7 +24,7 @@ import static com.google.common.collect.Iterables.find;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import com.google.common.collect.Iterables;
import com.google.common.collect.ImmutableSet;
import org.jclouds.Context;
import org.jclouds.View;
import org.jclouds.apis.ApiMetadata;
@ -83,7 +83,9 @@ public class Providers {
* @return all available providers
*/
public static Iterable<ProviderMetadata> all() {
return Iterables.concat(fromServiceLoader(), ProviderRegistry.fromRegistry());
return ImmutableSet.<ProviderMetadata>builder()
.addAll(fromServiceLoader())
.addAll(ProviderRegistry.fromRegistry()).build();
}
/**

View File

@ -0,0 +1,115 @@
/**
* 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 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 org.jclouds.apis.JcloudsTestComputeApiMetadata;
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 com.google.common.collect.ImmutableSet;
/**
*
* @author Adrian Cole
*
*/
public class BundlesTest {
@Test
public void testInstantiateAvailableClassesWhenAllAssignable() throws ClassNotFoundException {
Bundle bundle = createMock(Bundle.class);
expect(bundle.loadClass("org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata"))
.andReturn(JcloudsTestBlobStoreProviderMetadata.class);
expect(bundle.loadClass("org.jclouds.providers.JcloudsTestComputeProviderMetadata"))
.andReturn(JcloudsTestComputeProviderMetadata.class);
expect(bundle.loadClass("org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata"))
.andReturn(JcloudsTestYetAnotherComputeProviderMetadata.class);
replay(bundle);
Iterable<ProviderMetadata> providers = Bundles.instantiateAvailableClasses(bundle, ImmutableSet.of(
"org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata",
"org.jclouds.providers.JcloudsTestComputeProviderMetadata",
"org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata"), ProviderMetadata.class);
assertEquals(providers, ImmutableSet.of(new JcloudsTestBlobStoreProviderMetadata(),
new JcloudsTestComputeProviderMetadata(), new JcloudsTestYetAnotherComputeProviderMetadata()));
verify(bundle);
}
@Test
public void testInstantiateAvailableClassesWhenNotAllAssignable() throws ClassNotFoundException {
Bundle bundle = createMock(Bundle.class);
expect(bundle.loadClass("org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata"))
.andReturn(JcloudsTestBlobStoreProviderMetadata.class);
expect(bundle.loadClass("org.jclouds.apis.JcloudsTestComputeApiMetadata"))
.andReturn(JcloudsTestComputeApiMetadata.class);
expect(bundle.loadClass("org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata"))
.andReturn(JcloudsTestYetAnotherComputeProviderMetadata.class);
replay(bundle);
Iterable<ProviderMetadata> providers = Bundles.instantiateAvailableClasses(bundle, ImmutableSet.of(
"org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata",
"org.jclouds.apis.JcloudsTestComputeApiMetadata",
"org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata"), ProviderMetadata.class);
assertEquals(providers, ImmutableSet.of(new JcloudsTestBlobStoreProviderMetadata(),
new JcloudsTestYetAnotherComputeProviderMetadata()));
verify(bundle);
}
@Test
public void testStringsForResourcesInBundleWhenNoResources() throws Exception {
Bundle bundle = createMock(Bundle.class);
expect(bundle.getEntry("/META-INF/services/org.jclouds.apis.ApiMetadata")).andReturn(null);
replay(bundle);
assertEquals(Bundles.stringsForResorceInBundle("/META-INF/services/org.jclouds.apis.ApiMetadata", bundle),
ImmutableSet.of());
verify(bundle);
}
@Test
public void testStringsForResourcesInBundleWhenResourcePresent() throws Exception {
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"));
replay(bundle);
assertEquals(Bundles.stringsForResorceInBundle(
"/META-INF/services/org.jclouds.providers.ProviderMetadata", bundle), ImmutableSet.of(
"org.jclouds.providers.JcloudsTestBlobStoreProviderMetadata",
"org.jclouds.providers.JcloudsTestComputeProviderMetadata",
"org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata"));
verify(bundle);
}
}

View File

@ -18,6 +18,7 @@
*/
package org.jclouds.osgi;
import com.google.common.collect.Lists;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.apis.JcloudsTestBlobStoreApiMetadata;
import org.jclouds.apis.JcloudsTestComputeApiMetadata;
@ -27,6 +28,7 @@ import org.jclouds.providers.JcloudsTestComputeProviderMetadata;
import org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata;
import org.jclouds.providers.ProviderMetadata;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.testng.annotations.Test;
import java.net.MalformedURLException;
@ -34,16 +36,22 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
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;
import static org.testng.Assert.assertTrue;
/**
*
* @author iocanel
*
*/
public class MetadataBundleListenerTest {
@Test
@ -62,9 +70,8 @@ public class MetadataBundleListenerTest {
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<ProviderMetadata> providerMetadataList = listener.getProviderMetadata(bundle);
List<ProviderMetadata> providerMetadataList = Lists.newArrayList(listener.listProviderMetadata(bundle));
assertNotNull(providerMetadataList);
assertEquals(3, providerMetadataList.size());
assertTrue(providerMetadataList.contains(new JcloudsTestBlobStoreProviderMetadata()));
@ -73,6 +80,33 @@ public class MetadataBundleListenerTest {
verify(bundle);
}
@Test
public void testProviderListener() throws Exception {
MetadataBundleListener listener = new MetadataBundleListener();
ProviderListener providerListener = createMock(ProviderListener.class);
listener.addProviderListener(providerListener);
Bundle bundle = createMock(Bundle.class);
expect(bundle.getBundleId()).andReturn(10L).anyTimes();
expect(bundle.getEntry("/META-INF/services/org.jclouds.providers.ProviderMetadata")).andReturn(getClass().getResource("/META-INF/services/org.jclouds.providers.ProviderMetadata")).anyTimes();
expect(bundle.getEntry("/META-INF/services/org.jclouds.apis.ApiMetadata")).andReturn(null).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();
providerListener.added(anyObject(JcloudsTestBlobStoreProviderMetadata.class));
expectLastCall().times(1);
providerListener.added(anyObject(JcloudsTestComputeProviderMetadata.class));
expectLastCall().times(1);
providerListener.added(anyObject(JcloudsTestYetAnotherComputeProviderMetadata.class));
expectLastCall().times(1);
replay(bundle, providerListener);
BundleEvent event = new BundleEvent(BundleEvent.STARTED, bundle);
listener.bundleChanged(event);
verify(bundle, providerListener);
}
@Test
public void testGetProviderMetadataFromMultipleClassLoaders() throws Exception {
ClassLoader isolatedClassLoader = createIsolatedClassLoader();
@ -84,7 +118,7 @@ public class MetadataBundleListenerTest {
expect(bundle.loadClass("org.jclouds.providers.JcloudsTestYetAnotherComputeProviderMetadata")).andReturn(JcloudsTestYetAnotherComputeProviderMetadata.class).anyTimes();
replay(bundle);
List<ProviderMetadata> providerMetadataList = listener.getProviderMetadata(bundle);
List<ProviderMetadata> providerMetadataList = Lists.newArrayList(listener.listProviderMetadata(bundle));
assertNotNull(providerMetadataList);
assertEquals(2, providerMetadataList.size());
assertFalse(providerMetadataList.contains(new JcloudsTestBlobStoreProviderMetadata()));
@ -103,7 +137,7 @@ public class MetadataBundleListenerTest {
expect(bundle.loadClass("org.jclouds.apis.JcloudsTestYetAnotherComputeApiMetadata")).andReturn(JcloudsTestYetAnotherComputeApiMetadata.class).anyTimes();
replay(bundle);
List<ApiMetadata> apiMetadataList = listener.getApiMetadata(bundle);
List<ApiMetadata> apiMetadataList = Lists.newArrayList(listener.listApiMetadata(bundle));
assertNotNull(apiMetadataList);
assertEquals(3, apiMetadataList.size());
assertTrue(apiMetadataList.contains(new JcloudsTestBlobStoreApiMetadata()));
@ -112,6 +146,34 @@ public class MetadataBundleListenerTest {
verify(bundle);
}
@Test
public void testApiListener() throws Exception {
MetadataBundleListener listener = new MetadataBundleListener();
ApiListener apiListener = createMock(ApiListener.class);
listener.addApiListenerListener(apiListener);
Bundle bundle = createMock(Bundle.class);
expect(bundle.getBundleId()).andReturn(10L).anyTimes();
expect(bundle.getEntry("/META-INF/services/org.jclouds.providers.ProviderMetadata")).andReturn(null).anyTimes();
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();
apiListener.added(anyObject(JcloudsTestBlobStoreApiMetadata.class));
expectLastCall().times(1);
apiListener.added(anyObject(JcloudsTestBlobStoreApiMetadata.class));
expectLastCall().times(1);
apiListener.added(anyObject(JcloudsTestComputeApiMetadata.class));
expectLastCall().times(1);
replay(bundle, apiListener);
BundleEvent event = new BundleEvent(BundleEvent.STARTED, bundle);
listener.bundleChanged(event);
verify(bundle, apiListener);
}
@Test
public void testGetApiMetadataFromMultipleClassLoaders() throws Exception {
ClassLoader isolatedClassLoader = createIsolatedClassLoader();
@ -123,7 +185,8 @@ public class MetadataBundleListenerTest {
expect(bundle.loadClass("org.jclouds.apis.JcloudsTestYetAnotherComputeApiMetadata")).andReturn(JcloudsTestYetAnotherComputeApiMetadata.class).anyTimes();
replay(bundle);
List<ApiMetadata> apiMetadataList = listener.getApiMetadata(bundle);
List<ApiMetadata> apiMetadataList = Lists.newArrayList(listener.listApiMetadata(bundle));
assertNotNull(apiMetadataList);
assertEquals(2, apiMetadataList.size());
assertFalse(apiMetadataList.contains(new JcloudsTestBlobStoreApiMetadata()));

View File

@ -384,6 +384,7 @@
<ignoredResource>test.jks</ignoredResource>
<ignoredResource>CreateInternetService-options-test.xml</ignoredResource>
<ignoredResource>.gitattributes</ignoredResource>
<ignoredResource>OSGI-OPT/bnd.bnd</ignoredResource>
</ignoredResources>
<failBuildInCaseOfConflict>true</failBuildInCaseOfConflict>
</configuration>