package org.jboss.proxy.ejb;
import java.lang.reflect.Proxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.rmi.ServerException;
import javax.ejb.EJBHome;
import javax.ejb.EJBObject;
import javax.ejb.EJBMetaData;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.jboss.deployment.DeploymentException;
import org.jboss.ejb.Container;
import org.jboss.ejb.EJBProxyFactory;
import org.jboss.ejb.EJBProxyFactoryContainer;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.Invoker;
import org.jboss.invocation.InvocationContext;
import org.jboss.invocation.InvocationKey;
import org.jboss.logging.Logger;
import org.jboss.metadata.InvokerProxyBindingMetaData;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.EntityMetaData;
import org.jboss.metadata.SessionMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.naming.Util;
import org.jboss.proxy.Interceptor;
import org.jboss.proxy.ClientContainer;
import org.jboss.proxy.ClientContainerEx;
import org.jboss.proxy.IClientContainer;
import org.jboss.proxy.ejb.handle.HomeHandleImpl;
import org.jboss.system.Registry;
import org.jboss.util.NestedRuntimeException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class ProxyFactory
implements EJBProxyFactory
{
protected static final String HOME_INTERCEPTOR = "home";
protected static final String BEAN_INTERCEPTOR = "bean";
protected static final String LIST_ENTITY_INTERCEPTOR = "list-entity";
protected static Logger log = Logger.getLogger(ProxyFactory.class);
public EJBMetaData ejbMetaData;
protected boolean isServiceEndpointOnly;
protected EJBHome home;
protected EJBObject statelessObject;
protected String jndiBinding;
protected ObjectName jmxName;
protected int jmxNameHash;
private Integer jmxNameHashInteger;
protected Invoker beanInvoker;
protected Invoker homeInvoker;
protected InvokerProxyBindingMetaData invokerMetaData;
protected ArrayList homeInterceptorClasses = new ArrayList();
protected ArrayList beanInterceptorClasses = new ArrayList();
protected ArrayList listEntityInterceptorClasses = new ArrayList();
protected boolean includeIClientIface;
protected Container container;
protected Constructor proxyClassConstructor;
public void setContainer(Container con)
{
this.container = con;
}
public void setInvokerMetaData(InvokerProxyBindingMetaData metadata)
{
this.invokerMetaData = metadata;
}
public void setInvokerBinding(String binding)
{
this.jndiBinding = binding;
}
public void create() throws Exception
{
jmxName = container.getJmxName();
jmxNameHash = jmxName.hashCode();
jmxNameHashInteger = new Integer(jmxNameHash);
BeanMetaData bmd = container.getBeanMetaData();
boolean isSession = !(bmd instanceof EntityMetaData);
boolean isStatelessSession = false;
if(isSession)
{
SessionMetaData smd = (SessionMetaData) bmd;
if(bmd.getRemote() == null)
{
isServiceEndpointOnly = true;
return;
}
isStatelessSession = smd.isStateless();
}
Class pkClass = null;
if(!isSession)
{
EntityMetaData metaData = (EntityMetaData) bmd;
String pkClassName = metaData.getPrimaryKeyClass();
try
{
if(pkClassName != null)
{
pkClass = container.getClassLoader().loadClass(pkClassName);
}
else
{
pkClass
= container.getClassLoader()
.loadClass(metaData.getEjbClass())
.getField(metaData.getPrimKeyField())
.getClass();
}
}
catch(NoSuchFieldException e)
{
log.error(
"Unable to identify Bean's Primary Key class!"
+ " Did you specify a primary key class and/or field? Does that field exist?"
);
throw new RuntimeException("Primary Key Problem");
}
catch(NullPointerException e)
{
log.error(
"Unable to identify Bean's Primary Key class!"
+ " Did you specify a primary key class and/or field? Does that field exist?"
);
throw new RuntimeException("Primary Key Problem");
}
}
ejbMetaData = new EJBMetaDataImpl(
((EJBProxyFactoryContainer) container).getRemoteClass(),
((EJBProxyFactoryContainer) container).getHomeClass(),
pkClass, isSession, isStatelessSession, new HomeHandleImpl(jndiBinding)
);
if(log.isDebugEnabled())
{
log.debug("Proxy Factory for " + jndiBinding + " initialized");
}
initInterceptorClasses();
}
public void start() throws Exception
{
if(!isServiceEndpointOnly)
{
setupInvokers();
bindProxy();
}
}
protected void setupInvokers() throws Exception
{
ObjectName oname = new ObjectName(invokerMetaData.getInvokerMBean());
Invoker invoker = (Invoker) Registry.lookup(oname);
if(invoker == null)
{
throw new RuntimeException("invoker is null: " + oname);
}
homeInvoker = beanInvoker = invoker;
}
protected void initInterceptorClasses() throws Exception
{
HashMap interceptors = new HashMap();
Element proxyConfig = invokerMetaData.getProxyFactoryConfig();
Element clientInterceptors = MetaData.getOptionalChild(
proxyConfig,
"client-interceptors", null
);
if(clientInterceptors != null)
{
String value = MetaData.getElementAttribute(clientInterceptors, "exposeContainer");
this.includeIClientIface = Boolean.valueOf(value).booleanValue();
NodeList children = clientInterceptors.getChildNodes();
for(int i = 0; i < children.getLength(); i++)
{
Node currentChild = children.item(i);
if(currentChild.getNodeType() == Node.ELEMENT_NODE)
{
Element interceptor = (Element) children.item(i);
interceptors.put(interceptor.getTagName(), interceptor);
}
}
}
else
{
log.debug("client interceptors element is null");
}
Element homeInterceptorConf = (Element) interceptors.get(HOME_INTERCEPTOR);
loadInterceptorClasses(homeInterceptorClasses, homeInterceptorConf);
if(homeInterceptorClasses.size() == 0)
{
throw new DeploymentException("There are no home interface interceptors configured");
}
Element beanInterceptorConf = (Element) interceptors.get(BEAN_INTERCEPTOR);
loadInterceptorClasses(beanInterceptorClasses, beanInterceptorConf);
if(beanInterceptorClasses.size() == 0)
{
throw new DeploymentException("There are no bean interface interceptors configured");
}
Element listEntityInterceptorConf = (Element) interceptors.get(LIST_ENTITY_INTERCEPTOR);
loadInterceptorClasses(listEntityInterceptorClasses, listEntityInterceptorConf);
}
protected void loadInterceptorClasses(ArrayList classes, Element interceptors)
throws Exception
{
Iterator interceptorElements = MetaData.getChildrenByTagName(interceptors, "interceptor");
ClassLoader loader = container.getClassLoader();
while(interceptorElements != null && interceptorElements.hasNext())
{
Element ielement = (Element) interceptorElements.next();
String className = null;
className = MetaData.getElementContent(ielement);
String byValueAttr = MetaData.getElementAttribute(ielement, "call-by-value");
if(byValueAttr != null)
{
if (container.isCallByValue() == new Boolean(byValueAttr).booleanValue())
{
Class clazz = loader.loadClass(className);
classes.add(clazz);
}
}
else
{
Class clazz = loader.loadClass(className);
classes.add(clazz);
}
}
}
protected void loadInterceptorChain(ArrayList chain, ClientContainer client)
throws Exception
{
Interceptor last = null;
for(int i = 0; i < chain.size(); i++)
{
Class clazz = (Class) chain.get(i);
Interceptor interceptor = (Interceptor) clazz.newInstance();
if(last == null)
{
last = interceptor;
client.setNext(interceptor);
}
else
{
last.setNext(interceptor);
last = interceptor;
}
}
}
protected void bindProxy() throws Exception
{
try
{
InvocationContext context = new InvocationContext();
context.setObjectName(jmxNameHashInteger);
context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
context.setInvoker(homeInvoker);
context.setValue(InvocationKey.EJB_METADATA, ejbMetaData);
context.setInvokerProxyBinding(invokerMetaData.getName());
ClientContainer client = null;
EJBProxyFactoryContainer pfc = (EJBProxyFactoryContainer) container;
Class[] ifaces = {pfc.getHomeClass(), Class.forName("javax.ejb.Handle")};
if( includeIClientIface )
{
ifaces = new Class[] {IClientContainer.class, pfc.getHomeClass(),
Class.forName("javax.ejb.Handle")};
client = new ClientContainerEx(context);
}
else
{
client = new ClientContainer(context);
}
loadInterceptorChain(homeInterceptorClasses, client);
this.home = (EJBHome) Proxy.newProxyInstance(
pfc.getHomeClass().getClassLoader(),
ifaces,
client);
if(ejbMetaData.isStatelessSession() == true)
{
context = new InvocationContext();
context.setObjectName(jmxNameHashInteger);
context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
context.setInvoker(beanInvoker);
context.setInvokerProxyBinding(invokerMetaData.getName());
context.setValue(InvocationKey.EJB_HOME, home);
Class[] ssifaces = {pfc.getRemoteClass()};
if( includeIClientIface )
{
ssifaces = new Class[] {IClientContainer.class, pfc.getRemoteClass()};
client = new ClientContainerEx(context);
}
else
{
client = new ClientContainer(context);
}
loadInterceptorChain(beanInterceptorClasses, client);
this.statelessObject =
(EJBObject)Proxy.newProxyInstance(
pfc.getRemoteClass().getClassLoader(),
ssifaces,
client
);
}
else
{
Class[] intfs = {pfc.getRemoteClass()};
if( this.includeIClientIface )
{
intfs = new Class[]{IClientContainer.class, pfc.getRemoteClass()};
}
Class proxyClass = Proxy.getProxyClass(pfc.getRemoteClass().getClassLoader(), intfs);
final Class[] constructorParams = {InvocationHandler.class};
proxyClassConstructor = proxyClass.getConstructor(constructorParams);
}
rebindHomeProxy();
log.debug("Bound " + container.getBeanMetaData().getEjbName() + " to " + jndiBinding);
}
catch(Exception e)
{
throw new ServerException("Could not bind home", e);
}
}
protected void rebindHomeProxy() throws NamingException
{
log.debug("(re-)Binding Home " + jndiBinding);
Util.rebind(
new InitialContext(),
jndiBinding,
getEJBHome()
);
}
public void stop()
{
}
public void destroy()
{
if(!isServiceEndpointOnly)
{
try
{
InitialContext ctx = new InitialContext();
ctx.unbind(jndiBinding);
}
catch(Exception e)
{
}
homeInterceptorClasses.clear();
beanInterceptorClasses.clear();
listEntityInterceptorClasses.clear();
}
container = null;
ejbMetaData = null;
home = null;
statelessObject = null;
beanInvoker = null;
homeInvoker = null;
invokerMetaData = null;
}
public boolean isIdentical(Container container, Invocation mi)
{
throw new UnsupportedOperationException("TODO provide a default implementation");
}
public EJBMetaData getEJBMetaData()
{
return ejbMetaData;
}
public Object getEJBHome()
{
return home;
}
public Object getStatelessSessionEJBObject()
{
return statelessObject;
}
public Object getStatefulSessionEJBObject(Object id)
{
InvocationContext context = new InvocationContext();
context.setObjectName(jmxNameHashInteger);
context.setCacheId(id);
context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
context.setInvoker(beanInvoker);
log.debug("seting invoker proxy binding for stateful session: " + invokerMetaData.getName());
context.setInvokerProxyBinding(invokerMetaData.getName());
context.setValue(InvocationKey.EJB_HOME, home);
context.setValue("InvokerID", Invoker.ID);
ClientContainer client;
if( includeIClientIface )
{
client = new ClientContainerEx(context);
}
else
{
client = new ClientContainer(context);
}
try
{
loadInterceptorChain(beanInterceptorClasses, client);
}
catch(Exception e)
{
throw new NestedRuntimeException("Failed to load interceptor chain", e);
}
try
{
return (EJBObject) proxyClassConstructor.newInstance(new Object[]{client});
}
catch(Exception ex)
{
throw new NestedRuntimeException(ex);
}
}
public Object getEntityEJBObject(Object id)
{
Object result;
if(id == null)
{
result = null;
}
else
{
InvocationContext context = new InvocationContext();
context.setObjectName(jmxNameHashInteger);
context.setCacheId(id);
context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
context.setInvoker(beanInvoker);
context.setInvokerProxyBinding(invokerMetaData.getName());
context.setValue(InvocationKey.EJB_HOME, home);
ClientContainer client;
if( includeIClientIface )
{
client = new ClientContainerEx(context);
}
else
{
client = new ClientContainer(context);
}
try
{
loadInterceptorChain(beanInterceptorClasses, client);
}
catch(Exception e)
{
throw new NestedRuntimeException("Failed to load interceptor chain", e);
}
try
{
result = proxyClassConstructor.newInstance(new Object[]{client});
}
catch(Exception ex)
{
throw new NestedRuntimeException(ex);
}
}
return result;
}
public Collection getEntityCollection(Collection ids)
{
ArrayList list = new ArrayList(ids.size());
Iterator idEnum = ids.iterator();
while(idEnum.hasNext())
{
Object nextId = idEnum.next();
list.add(getEntityEJBObject(nextId));
}
return list;
}
}