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 */
017 package org.apache.activemq.util.osgi;
018
019 import java.io.IOException;
020 import java.io.InputStream;
021 import java.io.InputStreamReader;
022 import java.io.BufferedReader;
023 import java.util.Properties;
024 import java.util.ArrayList;
025 import java.util.concurrent.ConcurrentHashMap;
026 import java.util.concurrent.ConcurrentMap;
027 import java.net.URL;
028
029 import org.apache.activemq.Service;
030 import org.apache.activemq.store.PersistenceAdapter;
031 import org.apache.activemq.transport.Transport;
032 import org.apache.activemq.transport.discovery.DiscoveryAgent;
033 import org.apache.activemq.util.FactoryFinder;
034 import org.apache.activemq.util.FactoryFinder.ObjectFactory;
035 import org.slf4j.LoggerFactory;
036 import org.slf4j.Logger;
037
038 import org.osgi.framework.Bundle;
039 import org.osgi.framework.BundleActivator;
040 import org.osgi.framework.BundleContext;
041 import org.osgi.framework.BundleEvent;
042 import 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 */
049 public 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 }