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