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