/*
* (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
*
* $Id$
*/
package org.nuxeo.runtime.model.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.ExceptionUtils;
import org.nuxeo.runtime.RuntimeServiceException;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.model.Adaptable;
import org.nuxeo.runtime.model.Component;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.ComponentName;
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;
import org.nuxeo.runtime.service.TimestampedService;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class ComponentInstanceImpl implements ComponentInstance {
private static final Log log = LogFactory.getLog(ComponentInstanceImpl.class);
protected Object instance;
protected RegistrationInfoImpl ri;
protected List<OSGiServiceFactory> factories;
public ComponentInstanceImpl(RegistrationInfoImpl ri) {
this.ri = ri;
if (ri.implementation == null) {
// TODO: should be an extension component
instance = this;
} else {
// TODO: load class only once when creating the registration info
instance = createInstance();
}
}
@Override
public Object getInstance() {
switch (ri.state) {
case RegistrationInfo.RESOLVED:
// if not already activated activate it now
ri.activate();
return instance;
case RegistrationInfo.ACTIVATED:
return instance;
default:
return null;
}
}
public void create() {
if (ri.implementation == null) {
instance = this; // should be an extension component
} else {
// TODO: load class only once when creating the reshgitration info
instance = createInstance();
}
}
protected Object createInstance() {
try {
return ri.context.loadClass(ri.implementation).newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeServiceException(e);
}
}
@Override
public void destroy() {
deactivate();
instance = null;
ri = null;
factories = null;
}
@Override
public RuntimeContext getContext() {
return ri.context;
}
@Override
public ComponentName getName() {
return ri.name;
}
// TODO: cache info about implementation to avoid computing it each time
@Override
public void activate() {
// activate the implementation instance
try {
if (instance instanceof Component) {
((Component) instance).activate(this);
} else { // try by reflection
Method meth = instance.getClass().getDeclaredMethod("activate", ComponentContext.class);
meth.setAccessible(true);
meth.invoke(instance, this);
}
registerServices();
} catch (NoSuchMethodException e) {
// ignore this exception since the activate method is not mandatory
} catch (SecurityException | IllegalAccessException | InvocationTargetException e) {
handleError("Failed to activate component: " + getName(), e);
}
}
// TODO: cache info about implementation to avoid computing it each time
@Override
public void deactivate() {
// activate the implementation instance
try {
unregisterServices();
if (instance instanceof Component) {
((Component) instance).deactivate(this);
} else {
// try by reflection
Method meth = instance.getClass().getDeclaredMethod("deactivate", ComponentContext.class);
meth.setAccessible(true);
meth.invoke(instance, this);
}
} catch (NoSuchMethodException e) {
// ignore this exception since the activate method is not mandatory
} catch (SecurityException | IllegalAccessException | InvocationTargetException e) {
handleError("Failed to deactivate component: " + getName(), e);
}
}
@Override
public void reload() {
// activate the implementation instance
try {
Method meth = instance.getClass().getDeclaredMethod("reload", ComponentContext.class);
meth.setAccessible(true);
meth.invoke(instance, this);
} catch (NoSuchMethodException e) {
// ignore this exception since the reload method is not mandatory
} catch (ReflectiveOperationException e) {
handleError("Failed to reload component: " + getName(), e);
}
}
// TODO: cache info about implementation to avoid computing it each time
@Override
public void registerExtension(Extension extension) {
// if this the target extension point is extending another extension
// point from another component
// then delegate the registration to the that component component
ExtensionPoint xp = ri.getExtensionPoint(extension.getExtensionPoint());
if (xp != null) {
String superCo = xp.getSuperComponent();
if (superCo != null) {
((ExtensionImpl) extension).target = new ComponentName(superCo);
ri.manager.registerExtension(extension);
return;
}
// this extension is for us - register it
// activate the implementation instance
if (instance instanceof Component) {
((Component) instance).registerExtension(extension);
} else if (instance != this) {
// try by reflection, avoiding stack overflow
try {
Method meth = instance.getClass().getDeclaredMethod("registerExtension", Extension.class);
meth.setAccessible(true);
meth.invoke(instance, extension);
} catch (ReflectiveOperationException e) {
handleError("Error registering " + extension.getComponent().getName(), e);
}
}
} else {
String message = "Warning: target extension point '" + extension.getExtensionPoint() + "' of '"
+ extension.getTargetComponent().getName() + "' is unknown. Check your extension in component "
+ extension.getComponent().getName();
handleError(message, null);
}
}
// TODO: cache info about implementation to avoid computing it each time
@Override
public void unregisterExtension(Extension extension) {
// activate the implementation instance
if (instance instanceof Component) {
((Component) instance).unregisterExtension(extension);
} else if (instance != this) {
// try by reflection, avoiding stack overflow
try {
Method meth = instance.getClass().getDeclaredMethod("unregisterExtension", Extension.class);
meth.setAccessible(true);
meth.invoke(instance, extension);
} catch (ReflectiveOperationException e) {
handleError("Error unregistering " + extension.getComponent().getName(), e);
}
}
}
protected void handleError(String message, Exception e) {
Exception ee = e;
if (e != null) {
ee = ExceptionUtils.unwrapInvoke(e);
}
log.error(message, ee);
Framework.getRuntime().getErrors().add(message);
}
@Override
public <T> T getAdapter(Class<T> adapter) {
T res = null;
Object object = getInstance();
if (object instanceof Adaptable) {
res = ((Adaptable) object).getAdapter(adapter);
} else if (adapter.isAssignableFrom(object.getClass())) {
res = adapter.cast(object);
}
// to handle hot reload
if (res instanceof TimestampedService && object instanceof TimestampedService) {
Long lastModified = ((TimestampedService) object).getLastModified();
((TimestampedService) res).setLastModified(lastModified);
}
return res;
}
@Override
public String[] getPropertyNames() {
Set<String> set = ri.getProperties().keySet();
return set.toArray(new String[set.size()]);
}
@Override
public Property getProperty(String property) {
return ri.getProperties().get(property);
}
@Override
public RuntimeContext getRuntimeContext() {
return ri.getContext();
}
@Override
public Object getPropertyValue(String property) {
return getPropertyValue(property, null);
}
@Override
public Object getPropertyValue(String property, Object defValue) {
Property prop = getProperty(property);
if (prop != null) {
return prop.getValue();
} else {
return defValue;
}
}
@Override
public String[] getProvidedServiceNames() {
return ri.getProvidedServiceNames();
}
/**
* Register provided services as OSGi services
*/
public void registerServices() {
if (!Framework.isOSGiServiceSupported()) {
return;
}
String[] names = getProvidedServiceNames();
if (names != null && names.length > 0) {
factories = new ArrayList<ComponentInstanceImpl.OSGiServiceFactory>();
for (String className : names) {
OSGiServiceFactory factory = new OSGiServiceFactory(className);
factory.register();
factories.add(factory);
}
}
}
public void unregisterServices() {
// TODO the reload method is not reloading services. do we want this?
if (factories != null) {
for (OSGiServiceFactory factory : factories) {
factory.unregister();
}
factories = null;
}
}
@Override
public String toString() {
if (ri == null) {
return super.toString();
}
return ri.toString();
}
protected class OSGiServiceFactory implements ServiceFactory {
protected Class<?> clazz;
protected ServiceRegistration reg;
public OSGiServiceFactory(String className) {
this(ri.getContext().getBundle(), className);
}
public OSGiServiceFactory(Bundle bundle, String className) {
try {
clazz = ri.getContext().getBundle().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeServiceException(e);
}
}
@Override
public Object getService(Bundle bundle, ServiceRegistration registration) {
return getAdapter(clazz);
}
@Override
public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
// do nothing
}
public void register() {
reg = ri.getContext().getBundle().getBundleContext().registerService(clazz.getName(), this, null);
}
public void unregister() {
if (reg != null) {
reg.unregister();
}
reg = null;
}
}
}