/* * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Bogdan Stefanescu * Florent Guillaume */ package org.nuxeo.runtime.model.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.collections.ListenerList; import org.nuxeo.runtime.ComponentEvent; import org.nuxeo.runtime.ComponentListener; import org.nuxeo.runtime.RuntimeService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.ComponentManager; import org.nuxeo.runtime.model.ComponentName; import org.nuxeo.runtime.model.Extension; import org.nuxeo.runtime.model.RegistrationInfo; /** * @author Bogdan Stefanescu * @author Florent Guillaume */ public class ComponentManagerImpl implements ComponentManager { private static final Log log = LogFactory.getLog(ComponentManagerImpl.class); // must use an ordered Set to avoid loosing the order of the pending // extensions protected final Map<ComponentName, Set<Extension>> pendingExtensions; private ListenerList listeners; private final Map<String, RegistrationInfoImpl> services; protected Set<String> blacklist; protected ComponentRegistry reg; public ComponentManagerImpl(RuntimeService runtime) { reg = new ComponentRegistry(); pendingExtensions = new HashMap<ComponentName, Set<Extension>>(); listeners = new ListenerList(); services = new ConcurrentHashMap<String, RegistrationInfoImpl>(); blacklist = new HashSet<String>(); } @Override public synchronized Collection<RegistrationInfo> getRegistrations() { return new ArrayList<RegistrationInfo>(reg.getComponents()); } @Override public synchronized Map<ComponentName, Set<ComponentName>> getPendingRegistrations() { Map<ComponentName, Set<ComponentName>> pending = new HashMap<>(); for (Map.Entry<ComponentName, Set<ComponentName>> p : reg.getPendingComponents().entrySet()) { pending.put(p.getKey(), new LinkedHashSet<>(p.getValue())); } return pending; } @Override public synchronized Map<ComponentName, Set<Extension>> getMissingRegistrations() { Map<ComponentName, Set<Extension>> missing = new HashMap<>(); // also add pending extensions, not resolved because of missing target extension point for (Set<Extension> p : pendingExtensions.values()) { for (Extension e : p) { missing.computeIfAbsent(e.getComponent().getName(), k -> new LinkedHashSet<>()).add(e); } } return missing; } public synchronized Collection<ComponentName> getNeededRegistrations() { return pendingExtensions.keySet(); } public synchronized Collection<Extension> getPendingExtensions(ComponentName name) { return pendingExtensions.get(name); } @Override public synchronized RegistrationInfo getRegistrationInfo(ComponentName name) { return reg.getComponent(name); } @Override public synchronized boolean isRegistered(ComponentName name) { return reg.contains(name); } @Override public synchronized int size() { return reg.size(); } @Override public synchronized ComponentInstance getComponent(ComponentName name) { RegistrationInfo ri = reg.getComponent(name); return ri != null ? ri.getComponent() : null; } @Override public synchronized void shutdown() { ShutdownTask.shutdown(this); listeners = null; reg.destroy(); reg = null; } @Override public Set<String> getBlacklist() { return Collections.unmodifiableSet(blacklist); } @Override public void setBlacklist(Set<String> blacklist) { this.blacklist = blacklist; } @Override public synchronized void register(RegistrationInfo regInfo) { RegistrationInfoImpl ri = (RegistrationInfoImpl) regInfo; ComponentName name = ri.getName(); if (blacklist.contains(name.getName())) { log.warn("Component " + name.getName() + " was blacklisted. Ignoring."); return; } if (reg.contains(name)) { if (name.getName().startsWith("org.nuxeo.runtime.")) { // XXX we hide the fact that nuxeo-runtime bundles are // registered twice // TODO fix the root cause and remove this return; } handleError("Duplicate component name: " + name, null); return; } for (ComponentName n : ri.getAliases()) { if (reg.contains(n)) { handleError("Duplicate component name: " + n + " (alias for " + name + ")", null); return; } } ri.attach(this); try { log.info("Registering component: " + name); if (!reg.addComponent(ri)) { log.info("Registration delayed for component: " + name + ". Waiting for: " + reg.getMissingDependencies(ri.getName())); } } catch (RuntimeException e) { // don't raise this exception, // we want to isolate component errors from other components handleError("Failed to register component: " + name + " (" + e.toString() + ')', e); return; } } @Override public synchronized void unregister(RegistrationInfo regInfo) { unregister(regInfo.getName()); } @Override public synchronized void unregister(ComponentName name) { try { log.info("Unregistering component: " + name); reg.removeComponent(name); } catch (RuntimeException e) { log.error("Failed to unregister component: " + name, e); } } @Override public void addComponentListener(ComponentListener listener) { listeners.add(listener); } @Override public void removeComponentListener(ComponentListener listener) { listeners.remove(listener); } @Override public ComponentInstance getComponentProvidingService(Class<?> serviceClass) { RegistrationInfoImpl ri = services.get(serviceClass.getName()); if (ri == null) { return null; } if (ri.isResolved()) { // activate it first ri.activate(); } if (ri.isActivated()) { return ri.getComponent(); } log.debug("The component exposing the service " + serviceClass + " is not resolved or not started"); return null; } @Override public <T> T getService(Class<T> serviceClass) { ComponentInstance comp = getComponentProvidingService(serviceClass); return comp != null ? comp.getAdapter(serviceClass) : null; } @Override public Collection<ComponentName> getActivatingRegistrations() { return getRegistrations(RegistrationInfo.ACTIVATING); } @Override public Collection<ComponentName> getStartFailureRegistrations() { return getRegistrations(RegistrationInfo.START_FAILURE); } protected Collection<ComponentName> getRegistrations(int state) { RegistrationInfo[] comps = null; synchronized (this) { comps = reg.getComponentsArray(); } Collection<ComponentName> ret = new ArrayList<ComponentName>(); for (RegistrationInfo ri : comps) { if (ri.getState() == state) { ret.add(ri.getName()); } } return ret; } void sendEvent(ComponentEvent event) { log.debug("Dispatching event: " + event); Object[] listeners = this.listeners.getListeners(); for (Object listener : listeners) { ((ComponentListener) listener).handleEvent(event); } } public synchronized void registerExtension(Extension extension) { ComponentName name = extension.getTargetComponent(); RegistrationInfoImpl ri = reg.getComponent(name); if (ri != null && ri.component != null) { if (log.isDebugEnabled()) { log.debug("Register contributed extension: " + extension); } loadContributions(ri, extension); ri.component.registerExtension(extension); sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED, ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); } else { // put the extension in the pending queue if (log.isDebugEnabled()) { log.debug("Enqueue contributed extension to pending queue: " + extension); } Set<Extension> extensions = pendingExtensions.get(name); if (extensions == null) { extensions = new LinkedHashSet<Extension>(); // must keep order // in which // extensions are // contributed pendingExtensions.put(name, extensions); } extensions.add(extension); sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_PENDING, ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); } } public synchronized void unregisterExtension(Extension extension) { // TODO check if framework is shutting down and in that case do nothing if (log.isDebugEnabled()) { log.debug("Unregister contributed extension: " + extension); } ComponentName name = extension.getTargetComponent(); RegistrationInfo ri = reg.getComponent(name); if (ri != null) { ComponentInstance co = ri.getComponent(); if (co != null) { co.unregisterExtension(extension); } } else { // maybe it's pending Set<Extension> extensions = pendingExtensions.get(name); if (extensions != null) { // FIXME: extensions is a set of Extensions, not ComponentNames. extensions.remove(name); if (extensions.isEmpty()) { pendingExtensions.remove(name); } } } sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_UNREGISTERED, ((ComponentInstanceImpl) extension.getComponent()).ri, extension)); } public static void loadContributions(RegistrationInfoImpl ri, Extension xt) { ExtensionPointImpl xp = ri.getExtensionPoint(xt.getExtensionPoint()); if (xp != null && xp.contributions != null) { try { Object[] contribs = xp.loadContributions(ri, xt); xt.setContributions(contribs); } catch (RuntimeException e) { handleError("Failed to load contributions for component " + xt.getComponent().getName(), e); } } } public synchronized void registerServices(RegistrationInfoImpl ri) { if (ri.serviceDescriptor == null) { return; } for (String service : ri.serviceDescriptor.services) { log.info("Registering service: " + service); services.put(service, ri); // TODO: send notifications } } public synchronized void unregisterServices(RegistrationInfoImpl ri) { if (ri.serviceDescriptor == null) { return; } for (String service : ri.serviceDescriptor.services) { services.remove(service); // TODO: send notifications } } @Override public synchronized String[] getServices() { return services.keySet().toArray(new String[services.size()]); } protected static void handleError(String message, Exception e) { log.error(message, e); Framework.getRuntime().getWarnings().add(message); } }