/* * JBoss, Home of Professional Open Source. * Copyright 2008, 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.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.rmi.ServerException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import javax.ejb.EJBHome; import javax.ejb.EJBMetaData; import javax.ejb.EJBObject; 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.InvocationContext; import org.jboss.invocation.InvocationKey; import org.jboss.invocation.Invoker; import org.jboss.logging.Logger; import org.jboss.metadata.BeanMetaData; import org.jboss.metadata.EntityMetaData; import org.jboss.metadata.InvokerProxyBindingMetaData; import org.jboss.metadata.MetaData; import org.jboss.metadata.SessionMetaData; import org.jboss.naming.Util; import org.jboss.proxy.ClientContainer; import org.jboss.proxy.ClientContainerEx; import org.jboss.proxy.IClientContainer; import org.jboss.proxy.Interceptor; 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. * <p/> * 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. * <p/> * todo eliminate this class, at least in its present form. * * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a> * @author <a href="mailto:scott.stark@jboss.org">Scott Stark/a> * @author <a href="mailto:thomas.diesler@jboss.org">Thomas Diesler/a> * @version $Revision: 81030 $ */ 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) ); 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); } /** * The <code>loadInterceptorClasses</code> 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 <code>loadInterceptorChain</code> 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 <code>bindProxy</code> 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()); if(container.getSecurityManager() != null) { String secDomain = container.getSecurityManager().getSecurityDomain(); context.setValue(InvocationKey.SECURITY_DOMAIN, secDomain); } 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); if(container.getSecurityManager() != null) { String secDomain = container.getSecurityManager().getSecurityDomain(); context.setValue(InvocationKey.SECURITY_DOMAIN, secDomain); } 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(); } ejbMetaData = null; home = null; statelessObject = null; beanInvoker = null; homeInvoker = 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); if(container.getSecurityManager() != null) { String secDomain = container.getSecurityManager().getSecurityDomain(); context.setValue(InvocationKey.SECURITY_DOMAIN, secDomain); } 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); if(container.getSecurityManager() != null) { String secDomain = container.getSecurityManager().getSecurityDomain(); context.setValue(InvocationKey.SECURITY_DOMAIN, secDomain); } 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; } }