/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bogdan Stefanescu
* Florent Guillaume
*/
package org.eclipse.ecr.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.Hashtable;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.runtime.ComponentEvent;
import org.eclipse.ecr.runtime.ComponentListener;
import org.eclipse.ecr.runtime.RuntimeService;
import org.eclipse.ecr.runtime.api.Framework;
import org.eclipse.ecr.runtime.model.ComponentInstance;
import org.eclipse.ecr.runtime.model.ComponentManager;
import org.eclipse.ecr.runtime.model.ComponentName;
import org.eclipse.ecr.runtime.model.Extension;
import org.eclipse.ecr.runtime.model.RegistrationInfo;
import org.nuxeo.common.collections.ListenerList;
/**
* @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 Map<ComponentName, RegistrationInfoImpl> registry;
private Map<ComponentName, Set<RegistrationInfoImpl>> dependsOnMe;
private final Map<String, RegistrationInfoImpl> services;
protected Set<String> blacklist;
public ComponentManagerImpl(RuntimeService runtime) {
registry = new HashMap<ComponentName, RegistrationInfoImpl>();
dependsOnMe = new HashMap<ComponentName, Set<RegistrationInfoImpl>>();
pendingExtensions = new HashMap<ComponentName, Set<Extension>>();
listeners = new ListenerList();
services = new Hashtable<String, RegistrationInfoImpl>();
blacklist = new HashSet<String>();
}
@Override
public Collection<RegistrationInfo> getRegistrations() {
return new ArrayList<RegistrationInfo>(registry.values());
}
@Override
public Map<ComponentName, Set<ComponentName>> getPendingRegistrations() {
Map<ComponentName, Set<ComponentName>> pending = new HashMap<ComponentName, Set<ComponentName>>();
for (RegistrationInfo ri : registry.values()) {
if (ri.getState() == RegistrationInfo.REGISTERED) {
pending.put(ri.getName(), ri.getRequiredComponents());
}
}
for (Entry<ComponentName, Set<RegistrationInfoImpl>> e : dependsOnMe.entrySet()) {
for (RegistrationInfo ri : e.getValue()) {
pending.put(ri.getName(), Collections.singleton(e.getKey()));
}
}
for (Set<Extension> exts : pendingExtensions.values()) {
for (Extension ext : exts) {
pending.put(ext.getComponent().getName(),
Collections.singleton(ext.getTargetComponent()));
}
}
return pending;
}
public Collection<ComponentName> getNeededRegistrations() {
return pendingExtensions.keySet();
}
public Collection<Extension> getPendingExtensions(ComponentName name) {
return pendingExtensions.get(name);
}
@Override
public RegistrationInfo getRegistrationInfo(ComponentName name) {
return registry.get(name);
}
@Override
public synchronized boolean isRegistered(ComponentName name) {
return registry.containsKey(name);
}
@Override
public synchronized int size() {
return registry.size();
}
@Override
public ComponentInstance getComponent(ComponentName name) {
RegistrationInfoImpl ri = registry.get(name);
return ri != null ? ri.getComponent() : null;
}
@Override
public synchronized void shutdown() {
// unregister me -> this will unregister all objects that depends on me
List<RegistrationInfo> elems = new ArrayList<RegistrationInfo>(
registry.values());
for (RegistrationInfo ri : elems) {
try {
unregister(ri);
} catch (Exception e) {
log.error("failed to shutdown component manager", e);
}
}
try {
listeners = null;
registry.clear();
registry = null;
dependsOnMe.clear();
dependsOnMe = null;
} catch (Exception e) {
log.error("Failed to shutdown registry manager");
}
}
@Override
public synchronized void register(RegistrationInfo regInfo) {
_register((RegistrationInfoImpl) regInfo);
}
@Override
public Set<String> getBlacklist() {
return blacklist;
}
@Override
public void setBlacklist(Set<String> blacklist) {
this.blacklist = blacklist;
}
private void _register(RegistrationInfoImpl ri) {
ComponentName name = ri.getName();
if (blacklist.contains(name.getName())) {
log.warn("Component "+name.getName()+" was blacklisted. Ignoring.");
return;
}
if (isRegistered(name)) {
if (name.getName().startsWith("org.eclipse.ecr.runtime.")) {
// XXX we hide the fact that nuxeo-runtime bundles are
// registered twice
// TODO fix the root cause and remove this
return;
}
String msg = "Duplicate component name: " + name;
log.error(msg);
Framework.getRuntime().getWarnings().add(msg);
return;
//throw new IllegalStateException("Component was already registered: " + name);
}
ri.manager = this;
try {
ri.register();
} catch (Exception e) {
String msg = "Failed to register component: " + name;
log.error(msg, e);
msg += " (" + e.toString() + ')';
Framework.getRuntime().getWarnings().add(msg);
return;
}
// compute blocking dependencies
boolean hasBlockingDeps = computeBlockingDependencies(ri);
// check if blocking dependencies were found
if (!hasBlockingDeps) {
// check if there is any object waiting for me
Set<RegistrationInfoImpl> pendings = removeDependencies(name);
// update set the dependsOnMe member
ri.dependsOnMe = pendings;
// no blocking dependencies found - register it
log.info("Registering component: " + name);
// create the component
try {
registry.put(name, ri);
ri.resolve();
// if some objects are waiting for me notify them about my registration
if (ri.dependsOnMe != null) {
// notify all components that deonds on me about my registration
for (RegistrationInfoImpl pending : ri.dependsOnMe) {
if (pending.waitsFor == null) {
_register(pending);
} else {
// remove object dependence on me
pending.waitsFor.remove(name);
// if object has no more dependencies register it
if (pending.waitsFor.isEmpty()) {
pending.waitsFor = null;
_register(pending);
}
}
}
}
} catch (Throwable e) {
registry.remove(name);
String msg = "Failed to create component: " + name;
log.error(msg, e);
msg += " (" + e.toString() + ')';
Framework.getRuntime().getWarnings().add(msg);
}
} else {
log.info("Registration delayed for component: " + name
+ ". Waiting for: " + ri.waitsFor);
}
}
@Override
public synchronized void unregister(RegistrationInfo regInfo) {
_unregister((RegistrationInfoImpl) regInfo);
}
private void _unregister(RegistrationInfoImpl ri) {
// remove me as a dependent on other objects
if (ri.requires != null) {
for (ComponentName dep : ri.requires) {
RegistrationInfoImpl depRi = registry.get(dep);
if (depRi != null) { // can be null if comp is unresolved and waiting for this dep.
if (depRi.dependsOnMe != null) {
depRi.dependsOnMe.remove(ri);
}
}
}
}
// unresolve also the dependent objects
if (ri.dependsOnMe != null) {
List<RegistrationInfoImpl> deps = new ArrayList<RegistrationInfoImpl>(
ri.dependsOnMe);
for (RegistrationInfoImpl dep : deps) {
try {
dep.unresolve();
// TODO ------------- keep waiting comp. in the registry -
// otherwise the unresolved comp will never be unregistered
// add a blocking dependence on me
if (dep.waitsFor == null) {
dep.waitsFor = new HashSet<ComponentName>();
}
dep.waitsFor.add(ri.name);
addDependency(ri.name, dep);
// remove from registry
registry.remove(dep);
// TODO -------------
} catch (Exception e) {
log.error("Failed to unresolve component: " + dep.getName(), e);
}
}
}
log.info("Unregistering component: " + ri.name);
try {
if (registry.remove(ri.name) == null) {
// may be a pending component
//TODO -> put pending components in the registry
}
ri.unregister();
} catch (Exception e) {
log.error("Failed to unregister component: " + ri.getName(), e);
}
}
@Override
public synchronized void unregister(ComponentName name) {
RegistrationInfoImpl ri = registry.get(name);
if (ri != null) {
_unregister(ri);
}
}
@Override
public void addComponentListener(ComponentListener listener) {
listeners.add(listener);
}
@Override
public void removeComponentListener(ComponentListener listener) {
listeners.remove(listener);
}
@Override
public ComponentInstance getComponentProvidingService(Class<?> serviceClass) {
try {
RegistrationInfoImpl ri = services.get(serviceClass.getName());
if (ri != null) {
if (!ri.isActivated()) {
if (ri.isResolved()) {
ri.activate(); // activate the component if not yet activated
} else {
// Workaround to avoid messages during TypeService activation
if (!serviceClass.getSimpleName().equals("TypeProvider")) {
log.debug("The component exposing the service " +
serviceClass + " is not resolved");
}
return null;
}
}
return ri.getComponent();
}
} catch (Exception e) {
log.error("Failed to get service: " + serviceClass);
}
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() {
Collection<ComponentName> activating = new ArrayList<ComponentName>();
for (RegistrationInfo ri : registry.values()) {
if (ri.getState() == RegistrationInfo.ACTIVATING) {
activating.add(ri.getName());
}
}
return activating;
}
void sendEvent(ComponentEvent event) {
log.debug("Dispatching event: " + event);
Object[] listeners = this.listeners.getListeners();
for (Object listener : listeners) {
((ComponentListener) listener).handleEvent(event);
}
}
protected boolean computeBlockingDependencies(RegistrationInfoImpl ri) {
if (ri.requires != null) {
for (ComponentName dep : ri.requires) {
RegistrationInfoImpl depRi = registry.get(dep);
if (depRi == null) {
// dep is not yet registered - add it to the blocking deps queue
if (ri.waitsFor == null) {
ri.waitsFor = new HashSet<ComponentName>();
}
ri.waitsFor.add(dep);
addDependency(dep, ri);
} else {
// we need this when unregistering depRi
// to be able to unregister dependent components
if (depRi.dependsOnMe == null) {
depRi.dependsOnMe = new HashSet<RegistrationInfoImpl>();
}
depRi.dependsOnMe.add(ri);
}
}
}
return ri.waitsFor != null;
}
protected synchronized void addDependency(ComponentName name,
RegistrationInfoImpl dependent) {
Set<RegistrationInfoImpl> pendings = dependsOnMe.get(name);
if (pendings == null) {
pendings = new HashSet<RegistrationInfoImpl>();
dependsOnMe.put(name, pendings);
}
pendings.add(dependent);
}
protected synchronized Set<RegistrationInfoImpl> removeDependencies(
ComponentName name) {
return dependsOnMe.remove(name);
}
public void registerExtension(Extension extension) throws Exception {
ComponentName name = extension.getTargetComponent();
RegistrationInfoImpl ri = registry.get(name);
if (ri != 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 void unregisterExtension(Extension extension) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Unregister contributed extension: " + extension);
}
ComponentName name = extension.getTargetComponent();
RegistrationInfo ri = registry.get(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 (Exception e) {
log.error("Failed to create contribution objects", e);
}
}
}
public 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 void unregisterServices(RegistrationInfoImpl ri) {
if (ri.serviceDescriptor == null) {
return;
}
for (String service : ri.serviceDescriptor.services) {
services.remove(service);
// TODO: send notifications
}
}
@Override
public String[] getServices() {
return services.keySet().toArray(new String[services.size()]);
}
}