001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Bogdan Stefanescu
018 *     Ian Smith
019 *     Florent Guillaume
020 */
021
022package org.nuxeo.runtime.osgi;
023
024import java.util.IllegalFormatException;
025import java.util.LinkedList;
026import java.util.List;
027
028import org.apache.commons.lang.StringUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.osgi.framework.Bundle;
032import org.osgi.framework.BundleContext;
033import org.osgi.framework.BundleEvent;
034import org.osgi.framework.SynchronousBundleListener;
035
036/**
037 * @author Bogdan Stefanescu
038 * @author Ian Smith
039 * @author Florent Guillaume
040 */
041public class OSGiComponentLoader implements SynchronousBundleListener {
042
043    private static final Log log = LogFactory.getLog(OSGiComponentLoader.class);
044
045    private final OSGiRuntimeService runtime;
046
047    public OSGiComponentLoader(OSGiRuntimeService runtime) {
048        this.runtime = runtime;
049        install();
050    }
051
052    public void install() {
053        BundleContext ctx = runtime.getBundleContext();
054        ctx.addBundleListener(this);
055        Bundle[] bundles = ctx.getBundles();
056        int mask = Bundle.STARTING | Bundle.ACTIVE;
057        for (Bundle bundle : bundles) {
058            String name = bundle.getSymbolicName();
059            runtime.bundles.put(name, bundle);
060            int state = bundle.getState();
061            bundleDebug("Install bundle: %s " + bundleStateAsString(state), name);
062            if ((state & mask) != 0) { // check only resolved bundles
063                if (OSGiRuntimeService.getComponentsList(bundle) != null) {
064                    bundleDebug("Install bundle: %s component list: " + OSGiRuntimeService.getComponentsList(bundle),
065                            name);
066                    // check only bundles containing nuxeo comp.
067                    try {
068                        runtime.createContext(bundle);
069                    } catch (RuntimeException e) {
070                        // don't raise this exception,
071                        // we want to isolate bundle errors from other bundles
072                        log.warn("Failed to load components for bundle: " + name, e);
073                    }
074                } else {
075                    bundleDebug("Install bundle: %s has no components", name);
076                }
077            } else {
078                bundleDebug("Install bundle: %s is not STARTING " + "or ACTIVE, so no context was created", name);
079            }
080        }
081    }
082
083    public void uninstall() {
084        runtime.getBundleContext().removeBundleListener(this);
085    }
086
087    @Override
088    public void bundleChanged(BundleEvent event) {
089        String name = event.getBundle().getSymbolicName();
090        int type = event.getType();
091
092        bundleDebug("Bundle changed: %s " + bundleEventAsString(type), name);
093        try {
094            Bundle bundle = event.getBundle();
095            String componentsList = OSGiRuntimeService.getComponentsList(bundle);
096            switch (type) {
097            case BundleEvent.INSTALLED:
098                runtime.bundles.put(bundle.getSymbolicName(), bundle);
099                break;
100            case BundleEvent.UNINSTALLED:
101                runtime.bundles.remove(bundle.getSymbolicName());
102                break;
103            case BundleEvent.STARTING:
104            case BundleEvent.LAZY_ACTIVATION:
105                if (componentsList != null) {
106                    bundleDebug("Bundle changed: %s STARTING with components: " + componentsList, name);
107                    runtime.createContext(bundle);
108                } else {
109                    bundleDebug("Bundle changed: %s STARTING with no components", name);
110                }
111                break;
112            case BundleEvent.STOPPED:
113            case BundleEvent.UNRESOLVED:
114                if (componentsList != null) {
115                    bundleDebug("Bundle changed: %s STOPPING with components: " + componentsList, name);
116                    runtime.destroyContext(bundle);
117                } else {
118                    bundleDebug("Bundle changed: %s STOPPING with no components", name);
119                }
120                break;
121            }
122        } catch (RuntimeException e) {
123            log.error(e, e);
124        }
125    }
126
127    /**
128     * Used for generating good debug info. Convert bit vector into printable string.
129     *
130     * @param state bitwise-or of UNINSTALLED, INSTALLED, RESOLVED, STARTING, STOPPING, and ACTIVE
131     * @return printable version of bits that are on
132     */
133    public static String bundleStateAsString(int state) {
134        List<String> list = new LinkedList<String>();
135        if ((state & Bundle.UNINSTALLED) != 0) {
136            list.add("UNINSTALLED");
137        }
138        if ((state & Bundle.INSTALLED) != 0) {
139            list.add("INSTALLED");
140        }
141        if ((state & Bundle.RESOLVED) != 0) {
142            list.add("RESOLVED");
143        }
144        if ((state & Bundle.STARTING) != 0) {
145            list.add("STARTING");
146        }
147        if ((state & Bundle.STOPPING) != 0) {
148            list.add("STOPPING");
149        }
150        if ((state & Bundle.ACTIVE) != 0) {
151            list.add("ACTIVE");
152        }
153        return '[' + StringUtils.join(list, ',') + ']';
154    }
155
156    /**
157     * Used for generating good debug info. Convert event type into printable string.
158     *
159     * @param eventType INSTALLED, STARTED,STOPPED, UNINSTALLED,UPDATED
160     * @return printable version of event type
161     */
162    public static String bundleEventAsString(int eventType) {
163        switch (eventType) {
164        case BundleEvent.INSTALLED:
165            return "INSTALLED";
166        case BundleEvent.STARTED:
167            return "STARTED";
168        case BundleEvent.STARTING:
169            return "STARTING";
170        case BundleEvent.STOPPED:
171            return "STOPPED";
172        case BundleEvent.UNINSTALLED:
173            return "UNINSTALLED";
174        case BundleEvent.UPDATED:
175            return "UPDATED";
176        case BundleEvent.LAZY_ACTIVATION:
177            return "LAZY_ACTIVATION";
178        case BundleEvent.RESOLVED:
179            return "RESOLVED";
180        case BundleEvent.UNRESOLVED:
181            return "UNRESOLVED";
182        case BundleEvent.STOPPING:
183            return "STOPPING";
184        default:
185            return "UNKNOWN_OSGI_EVENT_TYPE_" + eventType;
186        }
187    }
188
189    /**
190     * Prints out a debug message for debugging bundles.
191     *
192     * @param msg the debug message with a %s in it which will be replaced by the component name
193     * @param name the component name
194     */
195    public static void bundleDebug(String msg, String name) {
196        if (log.isDebugEnabled()) {
197            try {
198                msg = String.format(msg, name);
199            } catch (IllegalFormatException e) {
200                // don't fail for this
201            }
202            log.debug(msg);
203        }
204    }
205
206}