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.List;
024    import java.util.Properties;
025    import java.util.ArrayList;
026    import java.util.concurrent.ConcurrentHashMap;
027    import java.util.concurrent.ConcurrentMap;
028    import java.net.URL;
029    
030    import org.apache.activemq.Service;
031    import org.apache.activemq.store.PersistenceAdapter;
032    import org.apache.activemq.transport.Transport;
033    import org.apache.activemq.transport.discovery.DiscoveryAgent;
034    import org.apache.activemq.util.FactoryFinder;
035    import org.apache.activemq.util.FactoryFinder.ObjectFactory;
036    import org.slf4j.LoggerFactory;
037    import org.slf4j.Logger;
038    
039    import org.osgi.framework.Bundle;
040    import org.osgi.framework.BundleActivator;
041    import org.osgi.framework.BundleContext;
042    import org.osgi.framework.BundleEvent;
043    import 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     */
050    public 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    }