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 *     Florent Guillaume
019 */
020
021package org.nuxeo.runtime.model.impl;
022
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.LinkedHashSet;
029import java.util.Map;
030import java.util.Set;
031import java.util.concurrent.ConcurrentHashMap;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.common.collections.ListenerList;
036import org.nuxeo.runtime.ComponentEvent;
037import org.nuxeo.runtime.ComponentListener;
038import org.nuxeo.runtime.RuntimeService;
039import org.nuxeo.runtime.api.Framework;
040import org.nuxeo.runtime.model.ComponentInstance;
041import org.nuxeo.runtime.model.ComponentManager;
042import org.nuxeo.runtime.model.ComponentName;
043import org.nuxeo.runtime.model.Extension;
044import org.nuxeo.runtime.model.RegistrationInfo;
045
046/**
047 * @author Bogdan Stefanescu
048 * @author Florent Guillaume
049 */
050public class ComponentManagerImpl implements ComponentManager {
051
052    private static final Log log = LogFactory.getLog(ComponentManagerImpl.class);
053
054    // must use an ordered Set to avoid loosing the order of the pending
055    // extensions
056    protected final Map<ComponentName, Set<Extension>> pendingExtensions;
057
058    private ListenerList listeners;
059
060    private final Map<String, RegistrationInfoImpl> services;
061
062    protected Set<String> blacklist;
063
064    protected ComponentRegistry reg;
065
066    public ComponentManagerImpl(RuntimeService runtime) {
067        reg = new ComponentRegistry();
068        pendingExtensions = new HashMap<ComponentName, Set<Extension>>();
069        listeners = new ListenerList();
070        services = new ConcurrentHashMap<String, RegistrationInfoImpl>();
071        blacklist = new HashSet<String>();
072    }
073
074    @Override
075    public synchronized Collection<RegistrationInfo> getRegistrations() {
076        return new ArrayList<RegistrationInfo>(reg.getComponents());
077    }
078
079    @Override
080    public synchronized Map<ComponentName, Set<ComponentName>> getPendingRegistrations() {
081        Map<ComponentName, Set<ComponentName>> pending = new HashMap<>();
082        for (Map.Entry<ComponentName, Set<ComponentName>> p : reg.getPendingComponents().entrySet()) {
083            pending.put(p.getKey(), new LinkedHashSet<>(p.getValue()));
084        }
085        return pending;
086    }
087
088    @Override
089    public synchronized Map<ComponentName, Set<Extension>> getMissingRegistrations() {
090        Map<ComponentName, Set<Extension>> missing = new HashMap<>();
091        // also add pending extensions, not resolved because of missing target extension point
092        for (Set<Extension> p : pendingExtensions.values()) {
093            for (Extension e : p) {
094                missing.computeIfAbsent(e.getComponent().getName(), k -> new LinkedHashSet<>()).add(e);
095            }
096        }
097        return missing;
098    }
099
100    public synchronized Collection<ComponentName> getNeededRegistrations() {
101        return pendingExtensions.keySet();
102    }
103
104    public synchronized Collection<Extension> getPendingExtensions(ComponentName name) {
105        return pendingExtensions.get(name);
106    }
107
108    @Override
109    public synchronized RegistrationInfo getRegistrationInfo(ComponentName name) {
110        return reg.getComponent(name);
111    }
112
113    @Override
114    public synchronized boolean isRegistered(ComponentName name) {
115        return reg.contains(name);
116    }
117
118    @Override
119    public synchronized int size() {
120        return reg.size();
121    }
122
123    @Override
124    public synchronized ComponentInstance getComponent(ComponentName name) {
125        RegistrationInfo ri = reg.getComponent(name);
126        return ri != null ? ri.getComponent() : null;
127    }
128
129    @Override
130    public synchronized void shutdown() {
131        ShutdownTask.shutdown(this);
132        listeners = null;
133        reg.destroy();
134        reg = null;
135    }
136
137    @Override
138    public Set<String> getBlacklist() {
139        return Collections.unmodifiableSet(blacklist);
140    }
141
142    @Override
143    public void setBlacklist(Set<String> blacklist) {
144        this.blacklist = blacklist;
145    }
146
147    @Override
148    public synchronized void register(RegistrationInfo regInfo) {
149        RegistrationInfoImpl ri = (RegistrationInfoImpl) regInfo;
150        ComponentName name = ri.getName();
151        if (blacklist.contains(name.getName())) {
152            log.warn("Component " + name.getName() + " was blacklisted. Ignoring.");
153            return;
154        }
155        if (reg.contains(name)) {
156            if (name.getName().startsWith("org.nuxeo.runtime.")) {
157                // XXX we hide the fact that nuxeo-runtime bundles are
158                // registered twice
159                // TODO fix the root cause and remove this
160                return;
161            }
162            handleError("Duplicate component name: " + name, null);
163            return;
164        }
165        for (ComponentName n : ri.getAliases()) {
166            if (reg.contains(n)) {
167                handleError("Duplicate component name: " + n + " (alias for " + name + ")", null);
168                return;
169            }
170        }
171
172        ri.attach(this);
173
174        try {
175            log.info("Registering component: " + name);
176            if (!reg.addComponent(ri)) {
177                log.info("Registration delayed for component: " + name + ". Waiting for: "
178                        + reg.getMissingDependencies(ri.getName()));
179            }
180        } catch (RuntimeException e) {
181            // don't raise this exception,
182            // we want to isolate component errors from other components
183            handleError("Failed to register component: " + name + " (" + e.toString() + ')', e);
184            return;
185        }
186    }
187
188    @Override
189    public synchronized void unregister(RegistrationInfo regInfo) {
190        unregister(regInfo.getName());
191    }
192
193    @Override
194    public synchronized void unregister(ComponentName name) {
195        try {
196            log.info("Unregistering component: " + name);
197            reg.removeComponent(name);
198        } catch (RuntimeException e) {
199            log.error("Failed to unregister component: " + name, e);
200        }
201    }
202
203    @Override
204    public void addComponentListener(ComponentListener listener) {
205        listeners.add(listener);
206    }
207
208    @Override
209    public void removeComponentListener(ComponentListener listener) {
210        listeners.remove(listener);
211    }
212
213    @Override
214    public ComponentInstance getComponentProvidingService(Class<?> serviceClass) {
215        RegistrationInfoImpl ri = services.get(serviceClass.getName());
216        if (ri == null) {
217            return null;
218        }
219        if (ri.isResolved()) {
220            // activate it first
221            ri.activate();
222        }
223        if (ri.isActivated()) {
224            return ri.getComponent();
225        }
226        log.debug("The component exposing the service " + serviceClass + " is not resolved or not started");
227        return null;
228    }
229
230    @Override
231    public <T> T getService(Class<T> serviceClass) {
232        ComponentInstance comp = getComponentProvidingService(serviceClass);
233        return comp != null ? comp.getAdapter(serviceClass) : null;
234    }
235
236    @Override
237    public Collection<ComponentName> getActivatingRegistrations() {
238        return getRegistrations(RegistrationInfo.ACTIVATING);
239    }
240
241    @Override
242    public Collection<ComponentName> getStartFailureRegistrations() {
243        return getRegistrations(RegistrationInfo.START_FAILURE);
244    }
245
246    protected Collection<ComponentName> getRegistrations(int state) {
247        RegistrationInfo[] comps = null;
248        synchronized (this) {
249            comps = reg.getComponentsArray();
250        }
251        Collection<ComponentName> ret = new ArrayList<ComponentName>();
252        for (RegistrationInfo ri : comps) {
253            if (ri.getState() == state) {
254                ret.add(ri.getName());
255            }
256        }
257        return ret;
258    }
259
260    void sendEvent(ComponentEvent event) {
261        log.debug("Dispatching event: " + event);
262        Object[] listeners = this.listeners.getListeners();
263        for (Object listener : listeners) {
264            ((ComponentListener) listener).handleEvent(event);
265        }
266    }
267
268    public synchronized void registerExtension(Extension extension) {
269        ComponentName name = extension.getTargetComponent();
270        RegistrationInfoImpl ri = reg.getComponent(name);
271        if (ri != null && ri.component != null) {
272            if (log.isDebugEnabled()) {
273                log.debug("Register contributed extension: " + extension);
274            }
275            loadContributions(ri, extension);
276            ri.component.registerExtension(extension);
277            sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED,
278                    ((ComponentInstanceImpl) extension.getComponent()).ri, extension));
279        } else {
280            // put the extension in the pending queue
281            if (log.isDebugEnabled()) {
282                log.debug("Enqueue contributed extension to pending queue: " + extension);
283            }
284            Set<Extension> extensions = pendingExtensions.get(name);
285            if (extensions == null) {
286                extensions = new LinkedHashSet<Extension>(); // must keep order
287                                                             // in which
288                                                             // extensions are
289                                                             // contributed
290                pendingExtensions.put(name, extensions);
291            }
292            extensions.add(extension);
293            sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_PENDING,
294                    ((ComponentInstanceImpl) extension.getComponent()).ri, extension));
295        }
296    }
297
298    public synchronized void unregisterExtension(Extension extension) {
299        // TODO check if framework is shutting down and in that case do nothing
300        if (log.isDebugEnabled()) {
301            log.debug("Unregister contributed extension: " + extension);
302        }
303        ComponentName name = extension.getTargetComponent();
304        RegistrationInfo ri = reg.getComponent(name);
305        if (ri != null) {
306            ComponentInstance co = ri.getComponent();
307            if (co != null) {
308                co.unregisterExtension(extension);
309            }
310        } else { // maybe it's pending
311            Set<Extension> extensions = pendingExtensions.get(name);
312            if (extensions != null) {
313                // FIXME: extensions is a set of Extensions, not ComponentNames.
314                extensions.remove(name);
315                if (extensions.isEmpty()) {
316                    pendingExtensions.remove(name);
317                }
318            }
319        }
320        sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_UNREGISTERED,
321                ((ComponentInstanceImpl) extension.getComponent()).ri, extension));
322    }
323
324    public static void loadContributions(RegistrationInfoImpl ri, Extension xt) {
325        ExtensionPointImpl xp = ri.getExtensionPoint(xt.getExtensionPoint());
326        if (xp != null && xp.contributions != null) {
327            try {
328                Object[] contribs = xp.loadContributions(ri, xt);
329                xt.setContributions(contribs);
330            } catch (RuntimeException e) {
331                handleError("Failed to load contributions for component " + xt.getComponent().getName(), e);
332            }
333        }
334    }
335
336    public synchronized void registerServices(RegistrationInfoImpl ri) {
337        if (ri.serviceDescriptor == null) {
338            return;
339        }
340        for (String service : ri.serviceDescriptor.services) {
341            log.info("Registering service: " + service);
342            services.put(service, ri);
343            // TODO: send notifications
344        }
345    }
346
347    public synchronized void unregisterServices(RegistrationInfoImpl ri) {
348        if (ri.serviceDescriptor == null) {
349            return;
350        }
351        for (String service : ri.serviceDescriptor.services) {
352            services.remove(service);
353            // TODO: send notifications
354        }
355    }
356
357    @Override
358    public synchronized String[] getServices() {
359        return services.keySet().toArray(new String[services.size()]);
360    }
361
362    protected static void handleError(String message, Exception e) {
363        log.error(message, e);
364        Framework.getRuntime().getWarnings().add(message);
365    }
366
367}