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