/*
* Copyright 2008 the original author or authors.
*
* 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.
*/
package org.rioproject.impl.bean;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.export.Exporter;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import org.rioproject.annotation.*;
import org.rioproject.config.Constants;
import org.rioproject.deploy.ServiceBeanInstantiationException;
import org.rioproject.impl.servicebean.ServiceBeanAdapter;
import org.rioproject.net.HostUtil;
import org.rioproject.proxy.bean.BeanDelegator;
import org.rioproject.servicebean.ServiceBean;
import org.rioproject.servicebean.ServiceBeanContext;
import org.rioproject.servicecore.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.management.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.rmi.Remote;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* The BeanAdapter provides a basic concrete implementation of a ServiceBean,
* and provides the support to delegate to a component, a <i>bean</i>
* (a Plain Old Java Object : POJO), making the bean remotable as Jini
* technology service using the dynamic service architecture provided by Rio.
*
* <p><b><u>Lifecycle Support</u></b><br>
* The BeanAdapter will invoke lifecycle methods on the bean if
* the bean has the following methods defined:
*
* <pre>
* public void preAdvertise();
* public void postUnAdvertise();
* public void preDestroy();
* </pre>
*
* <p>Alternatively, the bean can use the {@link org.rioproject.annotation.Initialized}, {@link org.rioproject.annotation.Started},
* {@link javax.annotation.PostConstruct}, {@link org.rioproject.annotation.PreAdvertise},
* {@link org.rioproject.annotation.PostUnAdvertise}, and {@link PreDestroy} annotations to be
* notified of each respective lifecycle event.
*
* <p>Note: ServiceBean initialization is invoked by the start method to
* initialize the ServiceBean. This method is called only once during the
* lifecycle of a ServiceBean
*
* <p><b><u>Properties and Context</u></b><br>
* Properties, the {@link net.jini.config.Configuration}, the
* {@link org.rioproject.servicebean.ServiceBeanContext} and the service bean itself
* can be injected into the bean as well. The following methods must be
* declared to have the respective properties injected into the bean:
* <pre>
* public void setParameters(Map<String, Object> parameters);
* public void setConfiguration(Configuration config);
* public void setServiceBeanContext(ServiceBeanContext context);
* public void setServiceBean(ServiceBean serviceBean);
* </pre>
*
* <p>Alternatively, the bean can use the {@link org.rioproject.annotation.SetConfiguration},
* {@link org.rioproject.annotation.SetParameters}, and {@link org.rioproject.annotation.SetServiceBeanContext}
* annotations as well.
*
* This property injection will be completed during the initialization of the
* ServiceBean.
*
* <p><b><u>Proxy support</u></b><br>
* The bean may also define a smart proxy. In order for the BeanAdapter to obtain
* the smart proxy the following method signatures must be defined:
*
* <pre>
* public Object createProxy(<proxy interface type>);
* </pre>
*
* Using the <tt>createProxy</tt> method, the method passes a remote reference
* to the exported back end implementation. This parameter can be declared to be
* the interface type your proxy implements, or a Object, which can then be
* narrowed to the interface type the bean implements. The method must return
* the bean's smart proxy, using the reference provided.
*
* <pre>
* public void setProxy(Object);
* </pre>
*
* If the bean has the <tt>setProxy</tt> method declared, this method will be
* invoked with the proxy that has been created for the bean.
*
* <p>Alternatively, the bean can use the {@link org.rioproject.annotation.CreateProxy}
* and {@link org.rioproject.annotation.SetProxy} annotations
*
* @see org.rioproject.servicebean.ServiceBean
* @see org.rioproject.impl.servicebean.ServiceBeanAdapter
*
* @author Dennis Reedy
*/
@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
public class BeanAdapter extends ServiceBeanAdapter {
private static final String COMPONENT = "org.rioproject.bean";
private static final Logger logger = LoggerFactory.getLogger(COMPONENT);
private Object bean;
private Remote delegatingProxy;
/**
* Create an instance of the BeanAdapter
*
* @param bean The bean, must not be null
*/
public BeanAdapter(Object bean) {
if(bean == null)
throw new IllegalArgumentException("bean is null");
this.bean = bean;
}
@Override
protected void registerMBean(ObjectName oName, MBeanServer mbeanServer)
throws NotCompliantMBeanException, MBeanRegistrationException, InstanceAlreadyExistsException {
String implClass = bean.getClass().getName();
Class[] ifaces = bean.getClass().getInterfaces();
Class mbean = null;
for (Class iface : ifaces) {
if (iface.getName().equals(implClass + "MBean")) {
mbean = iface;
break;
}
}
if(mbean!=null) {
String comment = context.getServiceBeanConfig().getComment();
mbeanServer.registerMBean(new AggregatingMBean(this, bean, mbean, comment), oName);
} else {
mbeanServer.registerMBean(this, oName);
}
}
/**
* Override the start method to create a delegating proxy required
* to navigate between the ServiceBean and the bean.
*
* <p>Once the bean has been started, the wrapped bean will be checked
* for {@code @Initialized()} or a {@code postStart()} method declaration. If the wrapped bean
* does have an accessible {@code @Initialized()} or a {@code postStart()} declared, it will be
* called following the parent's start method.
*
* @param context The ServiceBeanContext
* @return A remoted proxy used to communicate to the bean
* @throws ServiceBeanInstantiationException if starting the bean fails
*/
@Override
public Object start(final ServiceBeanContext context) throws ServiceBeanInstantiationException {
delegatingProxy = createDelegatingProxy();
Object o = super.start(context);
/* If defined, invoke postStart lifecycle method, Check if we are
* being started up first. If so, then the Cybernode will invoke
* the lifecycle method (RIO-141) */
Boolean rioStarting = (Boolean)context.getServiceBeanConfig().getConfigurationParameters().get(Constants.STARTING);
logger.trace("The bean [{}], is in the process of being instantiated: {}",
bean.getClass().getName(), (rioStarting==null?"false":rioStarting));
if(!(rioStarting!=null && rioStarting)) {
Class<? extends Annotation> annotation = BeanHelper.hasAnnotation(bean, PostConstruct.class)?
PostConstruct.class:Started.class;
BeanHelper.invokeLifeCycle(annotation, "postStart", bean);
}
return(o);
}
/**
* If the provided bean has the following method signatures defined,
* the corresponding properties will be injected into the bean:
* <pre>
* public void setParameters(Map parameters);
* public void setConfiguration(Configuration config);
* public void setServiceBeanContext(ServiceBeanContext context);
* </pre>
* <br>
* <p>Alternatively, the bean can use the {@link org.rioproject.annotation.SetConfiguration},
* {@link org.rioproject.annotation.SetParameters}, and
* {@link org.rioproject.annotation.SetServiceBeanContext} annotations as
* well.
*
* @param bean The bean to check for property injection
* @param context The {@link ServiceBeanContext}
*
* @throws IllegalArgumentException if either of the parameters are null
*/
public static void invokeLifecycleInjectors(Object bean,
ServiceBeanContext context) throws ServiceBeanInstantiationException {
if(bean==null)
throw new IllegalArgumentException("bean cannot be null");
if(context==null)
throw new IllegalArgumentException("ServiceBeanContext cannot be null");
/* Invoke the method with @SetParameters annotation or the setParameters method */
Map<String, ?> parameters = context.getServiceBeanConfig().getInitParameters();
BeanHelper.invokeBeanMethod(bean,
SetParameters.class,
"setParameters",
new Class[]{Map.class},
new Object[]{parameters});
/* Invoke the method with @SetConfiguration annotation or the setConfiguration method */
Configuration config ;
try {
config = context.getConfiguration();
} catch (ConfigurationException e) {
throw new ServiceBeanInstantiationException(e.getLocalizedMessage());
}
BeanHelper.invokeBeanMethod(bean,
SetConfiguration.class,
"setConfiguration",
new Class[]{Configuration.class},
new Object[]{config});
/* Invoke the method with @SetServiceBeanContext annotation or the setServiceBeanContext method */
BeanHelper.invokeBeanMethod(bean,
SetServiceBeanContext.class,
"setServiceBeanContext",
new Class[]{ServiceBeanContext.class},
new Object[]{context});
}
/**
* Override the initialize method to check if the wrapped bean has a
* {@code preInitialize()} method declared. If the wrapped bean
* does have an accessible {@code preInitialize()} declared, it
* will be called prior to initializing the bean.
*
* <p>Once the parent's initialize method has been invoked, if the wrapped
* bean has the following method signatures defined, the corresponding
* properties will be injected into the bean:
* <pre>
* public void setParameters(Map parameters);
* public void setConfiguration(Configuration config);
* public void setServiceBeanContext(ServiceBeanContext context);
* public void setServiceBean(ServiceBean serviceBean);
* </pre>
* <br>
*
* <p>Once bean initialization has been processed,
* the wrapped bean will be checked for {@code postInitialize()}
* method declaration. If the wrapped bean does have an accessible
* {@code postInitialize()} declared, it will be called following
* the parent's initialize method.
*
* @throws Exception if the initialization process fails
*/
@Override
public void initialize(final ServiceBeanContext context) throws Exception {
/* If defined, invoke preInitialize lifecycle method */
BeanHelper.invokeLifeCycle(null, "preInitialize", bean);
invokeLifecycleInjectors(bean, context);
super.initialize(context);
/* Invoke the setServiceBean method */
BeanHelper.invokeBeanMethod(bean,
SetServiceBean.class,
"setServiceBean",
new Class[]{ServiceBean.class},
new Object[]{this});
}
/**
* Override the advertise method to check if the wrapped bean has a
* {@code preAdvertise()} method declared. If the wrapped bean does have an accessible
* {@code preAdvertise()} declared, it will be called prior to
* advertising the bean.
*/
@Override
public void advertise() throws IOException {
try {
BeanHelper.invokeLifeCycle(PreAdvertise.class, "preAdvertise", bean);
} catch(Throwable t) {
String message = String.format("Invoking Bean [%s] preAdvertise lifecycle", bean.getClass().getName());
throw new IOException(message, t);
}
super.advertise();
}
/**
* Override the unadvertise method to check if the wrapped bean has a
* {@code postUnAdvertise()} method declared. If the wrapped bean does have an accessible
* {@code postUnAdvertise()} declared, it will be called following
* the parent's unadvertise method.
*/
@Override
public void unadvertise() throws IOException {
super.unadvertise();
/* If defined, invoke unadvertised lifecycle method */
try {
BeanHelper.invokeLifeCycle(PostUnAdvertise.class, "postUnAdvertise", bean);
} catch(Throwable t) {
String message = String.format("Invoking Bean [%s] postUnAdvertise lifecycle", bean.getClass().getName());
throw new IOException(message, t);
}
}
/**
* Override the destroy method to check if the wrapped bean has a
* {@code preDestroy()} method declared. If the wrapped bean does
* have an accessible {@code preDestroy()} declared, it will be
* called prior to destroying the bean.
*/
@Override
public void destroy() {
try {
BeanHelper.invokeLifeCycle(PreDestroy.class, "preDestroy", bean);
} catch(Exception e) {
String s = bean==null?"<unknown:null>":bean.getClass().getName();
logger.warn("Invoking Bean [{}] preDestroy()", s, e);
}
try {
super.destroy();
} finally {
bean = null;
if(delegatingProxy!=null) {
delegatingProxy = null;
}
}
}
/**
* Create the delegating proxy
*
* @return The proxy that will handle the delegation between the ServiceBean
* and the Bean (POJO)
*
* @throws RuntimeException
*/
protected Remote createDelegatingProxy() {
try {
Class[] interfaces = getInterfaceClasses(bean.getClass());
Remote proxy = (Remote) BeanDelegator.getInstance(this, bean, interfaces);
return(proxy);
} catch (Exception e) {
logger.info("exporting a standard proxy");
throw new RuntimeException("could not create proxy", e);
}
}
/*
* Get an array of classes the bean & service bean implement
*/
private static Class[] getInterfaceClasses(Class c)
throws ClassNotFoundException {
Set<Class> remotes = new HashSet<Class>();
Class[] interfaces = c.getInterfaces();
remotes.addAll(Arrays.asList(interfaces));
remotes.add(Service.class);
return(remotes.toArray(new Class[remotes.size()]));
}
/**
* Override exportDo, using our delegating proxy to handle
* invocations
*/
@Override
protected Remote exportDo(Exporter exporter) throws Exception {
if(exporter == null)
throw new IllegalArgumentException("exporter is null");
return (exporter.export(delegatingProxy));
}
/**
* Override getExporter, creating a BeanInvocationLayerFactory
* for the Exporter instead of of the BasicILFactory
*/
@Override
protected Exporter getExporter(Configuration config) throws Exception {
Exporter exporter = (Exporter)config.getEntry(COMPONENT,
"serverExporter",
Exporter.class,
null);
if(exporter==null) {
String host = HostUtil.getHostAddressFromProperty(Constants.RMI_HOST_ADDRESS);
exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(host, 0),
new BeanInvocationLayerFactory(),
false,
true);
}
logger.debug("[{}] using exporter {}", bean.getClass().getName(), exporter.toString());
return exporter;
}
/*
* Override createProxy to check if the bean has it's own proxy defined
*/
@Override
protected Object createProxy() {
Remote remoteRef = getExportedProxy();
Class proxyType;
Object proxy = getCustomProxy(bean, remoteRef);
if(proxy==null)
proxy = super.createProxy();
String setProxyMethodName="setProxy";
/* First check if the @SetProxy annotation is declared */
Method setProxy = BeanHelper.getAnnotatedMethod(bean, SetProxy.class);
if(setProxy!=null) {
proxyType = BeanHelper.getMethodFirstParamType(setProxy);
setProxyMethodName = setProxy.getName();
} else {
/* Else, if the setProxy method is declared, invoke it */
proxyType = BeanHelper.getMethodFirstParamType("setProxy", bean);
}
try {
if(proxyType!=null) {
BeanHelper.invokeBeanMethod(bean,
null,
setProxyMethodName,
new Class[]{proxyType},
new Object[]{proxy});
}
} catch (Exception e) {
logger.warn("Count not set bean proxy");
throw new RuntimeException("Could not set bean proxy", e);
}
return (proxy);
}
public static Object getCustomProxy(Object bean, Remote remoteRef) {
Object proxy = null;
Class proxyType;
String createProxyMethodName="createProxy";
/* First check if the @CreateProxy annotation is declared */
Method createProxy = BeanHelper.getAnnotatedMethod(bean,
CreateProxy.class);
if(createProxy!=null) {
proxyType = BeanHelper.getMethodFirstParamType(createProxy);
createProxyMethodName = createProxy.getName();
} else {
/* Else, if the createProxy method is declared, invoke it */
proxyType = BeanHelper.getMethodFirstParamType("createProxy", bean);
}
/* If declared, invoke the createProxy method */
try {
Object beanProxy = null;
if(proxyType!=null)
beanProxy = BeanHelper.invokeBeanMethod(bean,
null,
createProxyMethodName,
new Class[]{proxyType},
new Object[]{remoteRef});
if(beanProxy!=null) {
/* RIO-201 */
if(beanProxy instanceof Service) {
proxy = beanProxy;
} else {
Class[] interfaces = getInterfaceClasses(beanProxy.getClass());
proxy = BeanDelegator.getInstance(remoteRef,
beanProxy,
interfaces);
}
} /*else {
proxy = super.createProxy();
}*/
} catch (Exception e) {
logger.warn("Could not create bean proxy");
throw new RuntimeException("could not create bean proxy", e);
}
return proxy;
}
}