org.apache.activemq.util.osgi.Activator Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.util.osgi;
import static org.osgi.framework.wiring.BundleRevision.PACKAGE_NAMESPACE;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.activemq.Service;
import org.apache.activemq.store.PersistenceAdapter;
import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.discovery.DiscoveryAgent;
import org.apache.activemq.util.FactoryFinder;
import org.apache.activemq.util.FactoryFinder.ObjectFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An OSGi bundle activator for ActiveMQ which adapts the {@link org.apache.activemq.util.FactoryFinder}
* to the OSGi environment.
*
*/
public class Activator implements BundleActivator, SynchronousBundleListener, ObjectFactory {
private static final Logger LOG = LoggerFactory.getLogger(Activator.class);
private final ConcurrentMap> serviceCache = new ConcurrentHashMap>();
private final ConcurrentMap bundleWrappers = new ConcurrentHashMap();
private BundleContext bundleContext;
private Set packageCapabilities = new HashSet();
// ================================================================
// BundleActivator interface impl
// ================================================================
@Override
public synchronized void start(BundleContext bundleContext) throws Exception {
// This is how we replace the default FactoryFinder strategy
// with one that is more compatible in an OSGi env.
FactoryFinder.setObjectFactory(this);
debug("activating");
this.bundleContext = bundleContext;
cachePackageCapabilities(Service.class, Transport.class, DiscoveryAgent.class, PersistenceAdapter.class);
debug("checking existing bundles");
bundleContext.addBundleListener(this);
for (Bundle bundle : bundleContext.getBundles()) {
if (bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.STARTING ||
bundle.getState() == Bundle.ACTIVE || bundle.getState() == Bundle.STOPPING) {
register(bundle);
}
}
debug("activated");
}
/**
* Caches the package capabilities that are needed for a set of interface classes
*
* @param classes interfaces we want to track
*/
private void cachePackageCapabilities(Class> ... classes) {
BundleWiring ourWiring = bundleContext.getBundle().adapt(BundleWiring.class);
Set packageNames = new HashSet();
for (Class> clazz: classes) {
packageNames.add(clazz.getPackage().getName());
}
List ourExports = ourWiring.getCapabilities(PACKAGE_NAMESPACE);
for (BundleCapability ourExport : ourExports) {
String ourPkgName = (String) ourExport.getAttributes().get(PACKAGE_NAMESPACE);
if (packageNames.contains(ourPkgName)) {
packageCapabilities.add(ourExport);
}
}
}
@Override
public synchronized void stop(BundleContext bundleContext) throws Exception {
debug("deactivating");
bundleContext.removeBundleListener(this);
while (!bundleWrappers.isEmpty()) {
unregister(bundleWrappers.keySet().iterator().next());
}
debug("deactivated");
this.bundleContext = null;
}
// ================================================================
// SynchronousBundleListener interface impl
// ================================================================
@Override
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.RESOLVED) {
register(event.getBundle());
} else if (event.getType() == BundleEvent.UNRESOLVED || event.getType() == BundleEvent.UNINSTALLED) {
unregister(event.getBundle().getBundleId());
}
}
protected void register(final Bundle bundle) {
debug("checking bundle " + bundle.getBundleId());
if (isOurBundle(bundle) || isImportingUs(bundle) ) {
debug("Registering bundle for extension resolution: "+ bundle.getBundleId());
bundleWrappers.put(bundle.getBundleId(), new BundleWrapper(bundle));
}
}
private boolean isOurBundle(final Bundle bundle) {
return bundle.getBundleId() == bundleContext.getBundle().getBundleId();
}
/**
* When bundles unload.. we remove them thier cached Class entries from the
* serviceCache. Future service lookups for the service will fail.
*
* TODO: consider a way to get the Broker release any references to
* instances of the service.
*
* @param bundleId
*/
protected void unregister(long bundleId) {
BundleWrapper bundle = bundleWrappers.remove(bundleId);
if (bundle != null) {
for (String path : bundle.cachedServices) {
debug("unregistering service for key: " +path );
serviceCache.remove(path);
}
}
}
// ================================================================
// ObjectFactory interface impl
// ================================================================
@Override
public Object create(String path) throws IllegalAccessException, InstantiationException, IOException, ClassNotFoundException {
Class> clazz = serviceCache.get(path);
if (clazz == null) {
StringBuffer warnings = new StringBuffer();
// We need to look for a bundle that has that class.
int wrrningCounter=1;
for (BundleWrapper wrapper : bundleWrappers.values()) {
URL resource = wrapper.bundle.getResource(path);
if( resource == null ) {
continue;
}
Properties properties = loadProperties(resource);
String className = properties.getProperty("class");
if (className == null) {
warnings.append("("+(wrrningCounter++)+") Invalid service file in bundle "+wrapper+": 'class' property not defined.");
continue;
}
try {
clazz = wrapper.bundle.loadClass(className);
} catch (ClassNotFoundException e) {
warnings.append("("+(wrrningCounter++)+") Bundle "+wrapper+" could not load "+className+": "+e);
continue;
}
// Yay.. the class was found. Now cache it.
serviceCache.put(path, clazz);
wrapper.cachedServices.add(path);
break;
}
if( clazz == null ) {
// Since OSGi is such a tricky environment to work in.. lets give folks the
// most information we can in the error message.
String msg = "Service not found: '" + path + "'";
if (warnings.length()!= 0) {
msg += ", "+warnings;
}
throw new IOException(msg);
}
}
try {
return clazz.getConstructor().newInstance();
} catch (InvocationTargetException | NoSuchMethodException e) {
throw new InstantiationException(e.getMessage());
}
}
// ================================================================
// Internal Helper Methods
// ================================================================
private void debug(Object msg) {
LOG.debug(msg.toString());
}
private Properties loadProperties(URL resource) throws IOException {
InputStream in = resource.openStream();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
Properties properties = new Properties();
properties.load(in);
return properties;
} finally {
try {
in.close();
} catch (Exception e) {
}
}
}
/**
* We consider a bundle to be a candidate for objects if it imports at least
* one of the packages of our interfaces
*
* @param bundle
* @return true if the bundle is improting.
*/
private boolean isImportingUs(Bundle bundle) {
BundleWiring wiring = bundle.adapt(BundleWiring.class);
List imports = wiring.getRequiredWires(PACKAGE_NAMESPACE);
for (BundleWire importWire : imports) {
if (packageCapabilities.contains(importWire.getCapability())) {
return true;
}
}
return false;
}
private static class BundleWrapper {
private final Bundle bundle;
private final List cachedServices = new ArrayList();
public BundleWrapper(Bundle bundle) {
this.bundle = bundle;
}
}
}