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