001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.util.osgi; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.io.BufferedReader; 023import java.util.List; 024import java.util.Properties; 025import java.util.ArrayList; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ConcurrentMap; 028import java.net.URL; 029 030import org.apache.activemq.Service; 031import org.apache.activemq.store.PersistenceAdapter; 032import org.apache.activemq.transport.Transport; 033import org.apache.activemq.transport.discovery.DiscoveryAgent; 034import org.apache.activemq.util.FactoryFinder; 035import org.apache.activemq.util.FactoryFinder.ObjectFactory; 036import org.slf4j.LoggerFactory; 037import org.slf4j.Logger; 038 039import org.osgi.framework.Bundle; 040import org.osgi.framework.BundleActivator; 041import org.osgi.framework.BundleContext; 042import org.osgi.framework.BundleEvent; 043import org.osgi.framework.SynchronousBundleListener; 044 045/** 046 * An OSGi bundle activator for ActiveMQ which adapts the {@link org.apache.activemq.util.FactoryFinder} 047 * to the OSGi environment. 048 * 049 */ 050public class Activator implements BundleActivator, SynchronousBundleListener, ObjectFactory { 051 052 private static final Logger LOG = LoggerFactory.getLogger(Activator.class); 053 054 private final ConcurrentHashMap<String, Class> serviceCache = new ConcurrentHashMap<String, Class>(); 055 private final ConcurrentMap<Long, BundleWrapper> bundleWrappers = new ConcurrentHashMap<Long, BundleWrapper>(); 056 private BundleContext bundleContext; 057 058 // ================================================================ 059 // BundleActivator interface impl 060 // ================================================================ 061 062 public synchronized void start(BundleContext bundleContext) throws Exception { 063 064 // This is how we replace the default FactoryFinder strategy 065 // with one that is more compatible in an OSGi env. 066 FactoryFinder.setObjectFactory(this); 067 068 debug("activating"); 069 this.bundleContext = bundleContext; 070 debug("checking existing bundles"); 071 bundleContext.addBundleListener(this); 072 for (Bundle bundle : bundleContext.getBundles()) { 073 if (bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.STARTING || 074 bundle.getState() == Bundle.ACTIVE || bundle.getState() == Bundle.STOPPING) { 075 register(bundle); 076 } 077 } 078 debug("activated"); 079 } 080 081 082 public synchronized void stop(BundleContext bundleContext) throws Exception { 083 debug("deactivating"); 084 bundleContext.removeBundleListener(this); 085 while (!bundleWrappers.isEmpty()) { 086 unregister(bundleWrappers.keySet().iterator().next()); 087 } 088 debug("deactivated"); 089 this.bundleContext = null; 090 } 091 092 // ================================================================ 093 // SynchronousBundleListener interface impl 094 // ================================================================ 095 096 public void bundleChanged(BundleEvent event) { 097 if (event.getType() == BundleEvent.RESOLVED) { 098 register(event.getBundle()); 099 } else if (event.getType() == BundleEvent.UNRESOLVED || event.getType() == BundleEvent.UNINSTALLED) { 100 unregister(event.getBundle().getBundleId()); 101 } 102 } 103 104 protected void register(final Bundle bundle) { 105 debug("checking bundle " + bundle.getBundleId()); 106 if( !isImportingUs(bundle) ) { 107 debug("The bundle does not import us: "+ bundle.getBundleId()); 108 return; 109 } 110 bundleWrappers.put(bundle.getBundleId(), new BundleWrapper(bundle)); 111 } 112 113 /** 114 * When bundles unload.. we remove them thier cached Class entries from the 115 * serviceCache. Future service lookups for the service will fail. 116 * 117 * TODO: consider a way to get the Broker release any references to 118 * instances of the service. 119 * 120 * @param bundleId 121 */ 122 protected void unregister(long bundleId) { 123 BundleWrapper bundle = bundleWrappers.remove(bundleId); 124 if (bundle != null) { 125 for (String path : bundle.cachedServices) { 126 debug("unregistering service for key: " +path ); 127 serviceCache.remove(path); 128 } 129 } 130 } 131 132 // ================================================================ 133 // ObjectFactory interface impl 134 // ================================================================ 135 136 public Object create(String path) throws IllegalAccessException, InstantiationException, IOException, ClassNotFoundException { 137 Class clazz = serviceCache.get(path); 138 if (clazz == null) { 139 StringBuffer warnings = new StringBuffer(); 140 // We need to look for a bundle that has that class. 141 int wrrningCounter=1; 142 for (BundleWrapper wrapper : bundleWrappers.values()) { 143 URL resource = wrapper.bundle.getResource(path); 144 if( resource == null ) { 145 continue; 146 } 147 148 Properties properties = loadProperties(resource); 149 150 String className = properties.getProperty("class"); 151 if (className == null) { 152 warnings.append("("+(wrrningCounter++)+") Invalid service file in bundle "+wrapper+": 'class' property not defined."); 153 continue; 154 } 155 156 try { 157 clazz = wrapper.bundle.loadClass(className); 158 } catch (ClassNotFoundException e) { 159 warnings.append("("+(wrrningCounter++)+") Bundle "+wrapper+" could not load "+className+": "+e); 160 continue; 161 } 162 163 // Yay.. the class was found. Now cache it. 164 serviceCache.put(path, clazz); 165 wrapper.cachedServices.add(path); 166 break; 167 } 168 169 if( clazz == null ) { 170 // Since OSGi is such a tricky environment to work in.. lets give folks the 171 // most information we can in the error message. 172 String msg = "Service not found: '" + path + "'"; 173 if (warnings.length()!= 0) { 174 msg += ", "+warnings; 175 } 176 throw new IOException(msg); 177 } 178 } 179 return clazz.newInstance(); 180 } 181 182 // ================================================================ 183 // Internal Helper Methods 184 // ================================================================ 185 186 private void debug(Object msg) { 187 LOG.debug(msg.toString()); 188 } 189 190 private Properties loadProperties(URL resource) throws IOException { 191 InputStream in = resource.openStream(); 192 try { 193 BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); 194 Properties properties = new Properties(); 195 properties.load(in); 196 return properties; 197 } finally { 198 try { 199 in.close(); 200 } catch (Exception e) { 201 } 202 } 203 } 204 205 private boolean isImportingUs(Bundle bundle) { 206 return isImportingClass(bundle, Service.class) 207 || isImportingClass(bundle, Transport.class) 208 || isImportingClass(bundle, DiscoveryAgent.class) 209 || isImportingClass(bundle, PersistenceAdapter.class); 210 } 211 212 private boolean isImportingClass(Bundle bundle, Class clazz) { 213 try { 214 return bundle.loadClass(clazz.getName())==clazz; 215 } catch (ClassNotFoundException e) { 216 return false; 217 } 218 } 219 220 private static class BundleWrapper { 221 private final Bundle bundle; 222 private final List<String> cachedServices = new ArrayList<String>(); 223 224 public BundleWrapper(Bundle bundle) { 225 this.bundle = bundle; 226 } 227 } 228}