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            // check only resolved bundles
061            if ((state & mask) != 0) {
062                // check only bundles containing nuxeo components
063                if (OSGiRuntimeService.getComponentsList(bundle) != null) {
064                    log.debug("Install bundle: {} component list: {}", name,
065                            OSGiRuntimeService.getComponentsList(bundle));
066                    try {
067                        runtime.createContext(bundle);
068                    } catch (RuntimeException e) {
069                        // only log error to keep on deploying other bundles
070                        log.error(e, 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            // only log error to keep on deploying other bundles
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}