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    }