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