/* * (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: * Nuxeo - initial API and implementation */ package org.nuxeo.runtime.model.impl; import java.net.URL; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.xmap.annotation.XContent; import org.nuxeo.common.xmap.annotation.XNode; import org.nuxeo.common.xmap.annotation.XNodeList; import org.nuxeo.common.xmap.annotation.XNodeMap; import org.nuxeo.common.xmap.annotation.XObject; import org.nuxeo.runtime.ComponentEvent; import org.nuxeo.runtime.Version; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.Component; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.ComponentManager; import org.nuxeo.runtime.model.ComponentName; import org.nuxeo.runtime.model.ConfigurationDescriptor; import org.nuxeo.runtime.model.Extension; import org.nuxeo.runtime.model.ExtensionPoint; import org.nuxeo.runtime.model.Property; import org.nuxeo.runtime.model.RegistrationInfo; import org.nuxeo.runtime.model.RuntimeContext; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ @XObject("component") public class RegistrationInfoImpl implements RegistrationInfo { private static final long serialVersionUID = -4135715215018199522L; private static final Log log = LogFactory.getLog(RegistrationInfoImpl.class); // Note: some of these instance variables are accessed directly from other // classes in this package. transient ComponentManagerImpl manager; @XNode("@service") ServiceDescriptor serviceDescriptor; // the managed object name @XNode("@name") ComponentName name; @XNode("@disabled") boolean disabled; @XNode("configuration") ConfigurationDescriptor config; // the registration state int state = UNREGISTERED; // my aliases @XNodeList(value = "alias", type = HashSet.class, componentType = ComponentName.class) Set<ComponentName> aliases = new HashSet<>(); // the object names I depend of @XNodeList(value = "require", type = HashSet.class, componentType = ComponentName.class) Set<ComponentName> requires = new HashSet<>(); @XNode("implementation@class") String implementation; @XNodeList(value = "extension-point", type = ExtensionPointImpl[].class, componentType = ExtensionPointImpl.class) ExtensionPointImpl[] extensionPoints = new ExtensionPointImpl[0]; @XNodeList(value = "extension", type = ExtensionImpl[].class, componentType = ExtensionImpl.class) ExtensionImpl[] extensions = new ExtensionImpl[0]; @XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = Property.class) Map<String, Property> properties = new HashMap<>(); @XNode("@version") Version version = Version.ZERO; /** * To be set when deploying configuration components that are not in a bundle (e.g. from config. dir). Represent the * bundle that will be assumed to be the owner of the component. */ @XNode("@bundle") String bundle; @XContent("documentation") String documentation; URL xmlFileUrl; /** * This is used by the component persistence service to identify registration that was dynamically created and * persisted by users. */ boolean isPersistent; transient RuntimeContext context; // the managed component transient ComponentInstance component; public RegistrationInfoImpl() { } /** * Useful when dynamically registering components * * @param name the component name */ public RegistrationInfoImpl(ComponentName name) { this.name = name; } /** * Attach to a manager - this method must be called after all registration fields are initialized. * * @param manager */ public void attach(ComponentManagerImpl manager) { if (this.manager != null) { throw new IllegalStateException("Registration '" + name + "' was already attached to a manager"); } this.manager = manager; } public void setContext(RuntimeContext rc) { context = rc; } @Override public boolean isDisabled() { return disabled; } @Override public final boolean isPersistent() { return isPersistent; } @Override public void setPersistent(boolean isPersistent) { this.isPersistent = isPersistent; } public void destroy() { requires.clear(); aliases.clear(); properties.clear(); extensionPoints = new ExtensionPointImpl[0]; extensions = new ExtensionImpl[0]; version = null; component = null; name = null; manager = null; } public final boolean isDisposed() { return name == null; } @Override public ExtensionPoint[] getExtensionPoints() { return extensionPoints; } @Override public ComponentInstance getComponent() { return component; } /** * Reload the underlying component if reload is supported */ public synchronized void reload() { if (component != null) { component.reload(); } } @Override public ComponentName getName() { return name; } @Override public Map<String, Property> getProperties() { return properties; } public ExtensionPointImpl getExtensionPoint(String name) { for (ExtensionPointImpl xp : extensionPoints) { if (xp.name.equals(name)) { return xp; } } return null; } @Override public int getState() { return state; } @Override public Extension[] getExtensions() { return extensions; } @Override public Set<ComponentName> getAliases() { return aliases == null ? Collections.<ComponentName> emptySet() : aliases; } @Override public Set<ComponentName> getRequiredComponents() { return requires; } @Override public RuntimeContext getContext() { return context; } @Override public String getBundle() { return bundle; } @Override public Version getVersion() { return version; } @Override public String getDocumentation() { return documentation; } @Override public String toString() { return "RegistrationInfo: " + name; } @Override public ComponentManager getManager() { return manager; } synchronized void register() { if (state != UNREGISTERED) { return; } state = REGISTERED; manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_REGISTERED, this)); } synchronized void unregister() { if (state == UNREGISTERED) { return; } if (state == ACTIVATED || state == RESOLVED || state == START_FAILURE ) { unresolve(); } state = UNREGISTERED; manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNREGISTERED, this)); destroy(); } protected ComponentInstance createComponentInstance() { try { return new ComponentInstanceImpl(this); } catch (RuntimeException e) { String msg = "Failed to instantiate component: " + implementation; log.error(msg, e); msg += " (" + e.toString() + ')'; Framework.getRuntime().getErrors().add(msg); throw e; } } public synchronized void restart() { deactivate(); activate(); } @Override public int getApplicationStartedOrder() { if (component == null) { return 0; } Object ci = component.getInstance(); if (!(ci instanceof Component)) { return 0; } return ((Component) ci).getApplicationStartedOrder(); } @Override public void notifyApplicationStarted() { if (component != null) { Object ci = component.getInstance(); if (ci instanceof Component) { try { ((Component) ci).applicationStarted(component); } catch (RuntimeException e) { log.error(String.format("Component %s notification of application started failed: %s", component.getName(), e.getMessage()), e); state = START_FAILURE; } } } } @Override public void notifyApplicationStopped(Instant deadline) throws InterruptedException { if (component == null) { return; } Object ci = component.getInstance(); if (!(ci instanceof Component)) { return; } try { ((Component) ci).applicationStopped(component, deadline); } catch (RuntimeException e) { log.error(String.format("Component %s notification of application end of life failed: %s", component.getName(), e.getMessage()), e); } } public synchronized void activate() { if (state != RESOLVED) { return; } component = createComponentInstance(); state = ACTIVATING; manager.sendEvent(new ComponentEvent(ComponentEvent.ACTIVATING_COMPONENT, this)); // activate component component.activate(); log.info("Component activated: " + name); state = ACTIVATED; manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_ACTIVATED, this)); // register contributed extensions if any if (extensions != null) { checkExtensions(); for (Extension xt : extensions) { xt.setComponent(component); try { manager.registerExtension(xt); } catch (RuntimeException e) { String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName(); log.error(msg, e); msg += " (" + e.toString() + ')'; Framework.getRuntime().getErrors().add(msg); } } } // register pending extensions if any List<ComponentName> names = new ArrayList<>(1 + aliases.size()); names.add(name); names.addAll(aliases); for (ComponentName n : names) { Set<Extension> pendingExt = manager.pendingExtensions.remove(n); if (pendingExt == null) { continue; } for (Extension xt : pendingExt) { ComponentManagerImpl.loadContributions(this, xt); try { component.registerExtension(xt); } catch (RuntimeException e) { String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName(); log.error(msg, e); msg += " (" + e.toString() + ')'; Framework.getRuntime().getErrors().add(msg); } } } } public synchronized void deactivate() { if (state != ACTIVATED && state != START_FAILURE) { return; } state = DEACTIVATING; manager.sendEvent(new ComponentEvent(ComponentEvent.DEACTIVATING_COMPONENT, this)); // unregister contributed extensions if any if (extensions != null) { for (Extension xt : extensions) { try { manager.unregisterExtension(xt); } catch (RuntimeException e) { String message = "Failed to unregister extension. Contributor: " + xt.getComponent() + " to " + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint(); log.error(message, e); Framework.getRuntime().getErrors().add(message); } } } component.deactivate(); component = null; state = RESOLVED; manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_DEACTIVATED, this)); } public synchronized void resolve() { if (state != REGISTERED) { return; } // register services manager.registerServices(this); state = RESOLVED; manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_RESOLVED, this)); // TODO lazy activation activate(); } public synchronized void unresolve() { if (state == REGISTERED || state == UNREGISTERED) { return; } // un-register services manager.unregisterServices(this); if (state == ACTIVATED || state == START_FAILURE) { deactivate(); } state = REGISTERED; manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNRESOLVED, this)); } @Override // not synchronized, intermediate states from other synchronized methods // are not a problem public boolean isActivated() { return state == ACTIVATED; } @Override // not synchronized, intermediate states from other synchronized methods // are not a problem public boolean isResolved() { return state == RESOLVED; } @Override public String[] getProvidedServiceNames() { if (serviceDescriptor != null) { return serviceDescriptor.services; } return null; } public ServiceDescriptor getServiceDescriptor() { return serviceDescriptor; } @Override public String getImplementation() { return implementation; } public void checkExtensions() { // HashSet<String> targets = new HashSet<String>(); for (ExtensionImpl xt : extensions) { if (xt.target == null) { Framework.getRuntime().getWarnings().add( "Bad extension declaration (no target attribute specified). Component: " + getName()); continue; } // TODO do nothing for now -> fix the faulty components and then // activate these warnings // String key = xt.target.getName()+"#"+xt.getExtensionPoint(); // if (targets.contains(key)) { // multiple extensions to same // target point declared in same component // String message = // "Component "+getName()+" contains multiple extensions to "+key; // Framework.getRuntime().getWarnings().add(message); // //TODO: un-comment the following line if you want to treat this // as a dev. error // //Framework.handleDevError(new Error(message)); // } else { // targets.add(key); // } } } @Override public URL getXmlFileUrl() { return xmlFileUrl; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof RegistrationInfo) { return name.equals(((RegistrationInfo) obj).getName()); } return false; } @Override public int hashCode() { return name.hashCode(); } }