/* * JBoss, Home of Professional Open Source. * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ 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.util.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; /** * As we remove the one one association between container STACK and invoker we * keep this around. IN the future the creation of proxies is a task done on a * container basis but the container as a logical representation. In other * words, the container "Entity with RMI/IIOP" is not a container stack but * an association at the invocation level that points to all metadata for * a given container. *
* In other words this is here for legacy reason and to not disrupt the * container at once. * In particular we declare that we "implement" the container invoker * interface when we are just implementing the Proxy generation calls. * Separation of concern. * * todo eliminate this class, at least in its present form. * * @author Marc Fleury * @author Scott Stark/a> * @author Thomas Diesler/a> * @version $Revision: 61258 $ */ 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); // Metadata for the proxies public EJBMetaData ejbMetaData; // as of EJB2.1, we may have the case of web-service enabled beans without // remote interface, we will simply "mute" this factory in this case protected boolean isServiceEndpointOnly; protected EJBHome home; protected EJBObject statelessObject; // The name of the bean being deployed protected String jndiBinding; protected ObjectName jmxName; protected int jmxNameHash; private Integer jmxNameHashInteger; // The name of the delegate invoker // We have a beanInvoker and homeInvoker // because clustering has a different invoker for each // and we want to reuse code here. protected Invoker beanInvoker; protected Invoker homeInvoker; protected InvokerProxyBindingMetaData invokerMetaData; /** * The proxy-config/client-interceptors/home stack */ protected ArrayList homeInterceptorClasses = new ArrayList(); /** * The proxy-config/client-interceptors/bean stack */ protected ArrayList beanInterceptorClasses = new ArrayList(); /** * The proxy-config/client-interceptors/entity-list stack */ protected ArrayList listEntityInterceptorClasses = new ArrayList(); /** A flag indicating if the IClientContainer interface should be added */ protected boolean includeIClientIface; // A pointer to the container this proxy factory is dedicated to protected Container container; protected Constructor proxyClassConstructor; // Container plugin implementation ----------------------------------------- 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); // Create metadata BeanMetaData bmd = container.getBeanMetaData(); boolean isSession = !(bmd instanceof EntityMetaData); boolean isStatelessSession = false; if(isSession) { SessionMetaData smd = (SessionMetaData) bmd; if(bmd.getRemote() == null) { isServiceEndpointOnly = true; // nothing more to do 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, //null if not entity isSession, //Session isStatelessSession, //Stateless new HomeHandleImpl(jndiBinding) ); if(log.isDebugEnabled()) { log.debug("Proxy Factory for " + jndiBinding + " initialized"); } initInterceptorClasses(); } /** * Become fully available. At this point our invokers should be started * and we can bind the homes into JNDI. */ public void start() throws Exception { if(!isServiceEndpointOnly) { setupInvokers(); bindProxy(); } } /** * Lookup the invokers in the object registry. This typically cannot * be done until our start method as the invokers may need to be started * themselves. */ 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; } /** * Load the client interceptor classes */ 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); } /** * TheloadInterceptorClasses
load an interceptor classes from
* configuration
*
* @throws Exception if an error occurs
*/
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);
// load the invoker interceptor that corresponds to the beans call semantic
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);
}
}
}
/**
* The loadInterceptorChain
create instances of interceptor
* classes previously loaded in loadInterceptorClasses
*
* @throws Exception if an error occurs
*/
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;
}
}
}
/**
* The bindProxy
method creates the home proxy and binds
* the home into jndi. It also creates the InvocationContext and client
* container and interceptor chain.
*
* @throws Exception if an error occurs
*/
protected void bindProxy() throws Exception
{
try
{
// Create a stack from the description (in the future) for now we hardcode it
InvocationContext context = new InvocationContext();
context.setObjectName(jmxNameHashInteger);
context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
// The behavior for home proxying should be isolated in an interceptor FIXME
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);
// Create the EJBHome
this.home = (EJBHome) Proxy.newProxyInstance(
// Class loader pointing to the right classes from deployment
pfc.getHomeClass().getClassLoader(),
// The classes we want to implement home and handle
ifaces,
// The home proxy as invocation handler
client);
// Create stateless session object
// Same instance is used for all objects
if(ejbMetaData.isStatelessSession() == true)
{
// Create a stack from the description (in the future) for now we hardcode it
context = new InvocationContext();
context.setObjectName(jmxNameHashInteger);
context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
// The behavior for home proxying should be isolated in an interceptor FIXME
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(
// Correct CL
pfc.getRemoteClass().getClassLoader(),
// Interfaces
ssifaces,
// SLSB proxy as invocation handler
client
);
}
else
{
// this is faster than newProxyInstance
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);
}
// Bind the home in the JNDI naming space
rebindHomeProxy();
}
catch(Exception e)
{
throw new ServerException("Could not bind home", e);
}
}
protected void rebindHomeProxy() throws NamingException
{
// (Re-)Bind the home in the JNDI naming space
log.debug("(re-)Binding Home " + jndiBinding);
Util.rebind(
// The context
new InitialContext(),
// Jndi name
jndiBinding,
// The Home
getEJBHome()
);
log.info("Bound EJB Home '" + container.getBeanMetaData().getEjbName() + "' to jndi '" + jndiBinding + "'");
}
public void stop()
{
}
public void destroy()
{
if(!isServiceEndpointOnly)
{
log.info("Unbind EJB Home '" + container.getBeanMetaData().getEjbName() + "' from jndi '" + jndiBinding + "'");
try
{
InitialContext ctx = new InitialContext();
ctx.unbind(jndiBinding);
}
catch(Exception e)
{
// ignore.
}
homeInterceptorClasses.clear();
beanInterceptorClasses.clear();
listEntityInterceptorClasses.clear();
}
container = null;
ejbMetaData = null;
home = null;
statelessObject = null;
beanInvoker = null;
homeInvoker = null;
invokerMetaData = null;
proxyClassConstructor = null;
}
// EJBProxyFactory implementation -------------------------------------
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;
}
/**
* Return the EJBObject proxy for stateless sessions.
*/
public Object getStatelessSessionEJBObject()
{
return statelessObject;
}
/**
* Create an EJBObject proxy for a stateful session given its session id.
*/
public Object getStatefulSessionEJBObject(Object id)
{
// Create a stack from the description (in the future) for now we hardcode it
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);
}
}
/**
* Create an EJBObject proxy for an entity given its primary key.
*/
public Object getEntityEJBObject(Object id)
{
Object result;
if(id == null)
{
result = null;
}
else
{
// Create a stack from the description (in the future) for now we hardcode it
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;
}
/**
* Create a Collection EJBObject proxies for an entity given its primary keys.
*/
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;
}
}