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}