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