/* * JBoss, the OpenSource EJB server * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.ejb.plugins.jrmp.interfaces; import java.io.IOException; import java.io.Externalizable; import java.io.ObjectInput; import java.io.ObjectOutput; import java.rmi.MarshalledObject; import java.util.HashMap; import java.lang.reflect.Method; import java.security.Principal; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.SystemException; import javax.naming.InitialContext; import javax.naming.NamingException; import org.jboss.ejb.plugins.jrmp.server.JRMPContainerInvoker; import org.jboss.tm.TransactionPropagationContextFactory; import org.jboss.security.SecurityAssociation; /** * Abstract superclass of JRMP client-side proxies. * * @see ContainerRemote * * @author Rickard Öberg (rickard.oberg@telkel.com) * @author Jason Dillon <jason@planet57.com> * * @version $Revision: 1.11 $ */ public abstract class GenericProxy implements Externalizable { // Constants ----------------------------------------------------- /** Serial Version Identifier. */ private static final long serialVersionUID = 1870461898442160570L; // Attributes ---------------------------------------------------- // Static -------------------------------------------------------- /** An empty method parameter list. */ protected static final Object[] EMPTY_ARGS = {}; /** {@link Object#toString} method reference. */ protected static final Method TO_STRING; /** {@link Object#hashCode} method reference. */ protected static final Method HASH_CODE; /** {@link Object#equals} method reference. */ protected static final Method EQUALS; /** * Initialize {@link Object} method references. */ static { try { final Class[] empty = {}; final Class type = Object.class; TO_STRING = type.getMethod("toString", empty); HASH_CODE = type.getMethod("hashCode", empty); EQUALS = type.getMethod("equals", new Class[] { type }); } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError(e); } } /** * Our transaction manager. * * When set to a non-null value, this is used for getting the * transaction to use for optimized local method invocations. * If null, transactions are not propagated on * optimized local method invocations. */ private static TransactionManager tm = null; /** * Factory for transaction propagation contexts. * * When set to a non-null value, it is used to get transaction * propagation contexts for remote method invocations. * If null, transactions are not propagated on * remote method invocations. */ protected static TransactionPropagationContextFactory tpcFactory = null; /** * This map maps JNDI names of containers to the remote interfaces * of the container invokers of these containers. */ private static HashMap invokers = new HashMap(); // Prevent DGC /** * Return the remote interface of the container invoker for the * container with the given JNDI name. */ private static ContainerRemote getLocal(String jndiName) { return (ContainerRemote)invokers.get(jndiName); } /** * Add an invoker to the invokers map. */ public static void addLocal(String jndiName, ContainerRemote invoker) { invokers.put(jndiName, invoker); } /** * Remove an invoker from the invokers map. */ public static void removeLocal(String jndiName) { invokers.remove(jndiName); } /** * Set the transaction manager. */ public static void setTransactionManager(TransactionManager txMan) { tm = txMan; } /** * Set the transaction propagation context factory. */ public static void setTPCFactory(TransactionPropagationContextFactory tpcf) { tpcFactory = tpcf; } // Constructors -------------------------------------------------- /** * A public, no-args constructor for externalization to work. */ public GenericProxy() { // For externalization to work } /** * Create a new GenericProxy. * * @param name * The JNDI name of the container that we proxy for. * @param container * The remote interface of the container invoker of the * container we proxy for. * @param optimize * If true, this proxy will attempt to optimize * VM-local calls. */ protected GenericProxy(String name, ContainerRemote container, boolean optimize) { this.name = name; this.container = container; this.optimize = optimize; // get a context handle this.initialContextHandle = InitialContextHandle.create(); } // Public -------------------------------------------------------- /** * Externalize this instance. * * If this instance lives in a different VM than its container * invoker, the remote interface of the container invoker is * not externalized. */ public void writeExternal(final ObjectOutput out) throws IOException { out.writeUTF(name); out.writeObject(isLocal() ? container : null); out.writeLong(containerStartup); out.writeBoolean(optimize); out.writeObject(initialContextHandle); } /** * Un-externalize this instance. * * If this instance is deserialized in the same VM as its container * invoker, the remote interface of the container invoker is * restored by looking up the name in the invokers map. */ public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); container = (ContainerRemote)in.readObject(); containerStartup = in.readLong(); optimize = in.readBoolean(); initialContextHandle = (InitialContextHandle)in.readObject(); if (isLocal()) { // VM-local optimization; still follows RMI-semantics though container = getLocal(name); } } // Package protected --------------------------------------------- /** * Return the principal to use for invocations with this proxy. */ protected Principal getPrincipal() { return SecurityAssociation.getPrincipal(); } /** * Return the credentials to use for invocations with this proxy. */ protected Object getCredential() { return SecurityAssociation.getCredential(); } /** * Return the transaction associated with the current thread. * Returns null if the transaction manager was never * set, or if no transaction is associated with the current thread. */ protected Transaction getTransaction() throws SystemException { return (tm == null) ? null : tm.getTransaction(); } /** * Return the transaction propagation context of the transaction * associated with the current thread. * Returns null if the transaction manager was never * set, or if no transaction is associated with the current thread. */ protected Object getTransactionPropagationContext() throws SystemException { return (tpcFactory == null) ? null : tpcFactory.getTransactionPropagationContext(); } // Protected ----------------------------------------------------- /** * The JNDI name of the container that we proxy for. */ protected String name; /** * The remote interface of the container invoker of the container * we proxy for. */ protected ContainerRemote container; /** * If true, this proxy will attempt to optimize * VM-local calls. */ protected boolean optimize; // = false; /** * Provides access to the correct naming context for handle objects. */ protected InitialContextHandle initialContextHandle; /** * Returns true iff this instance lives in the same * VM as its container. */ protected boolean isLocal() { return containerStartup == ContainerRemote.STARTUP; } /** * Create an InitialContext using the saved environment or * create a vanilla InitialContext when the enviroment * is null. * * @return InitialContext suitable for the bean that this * is a proxy for. * * @throws NamingException Failed to create InitialContext. */ protected InitialContext createInitialContext() throws NamingException { return initialContextHandle.getInitialContext(); } /** * Invoke the container to handle this method invocation. * *

If optimization is enabled and this is a local proxy, then the * container is invoked directly, else a remote call is made. * * @param id ??? * @param method The method to invoke. * @param args The arguments passed to the method. * * @throws Throwable Failed to invoke container. */ protected Object invokeContainer(final Object id, final Method method, final Object[] args) throws Throwable { Object result; // optimize if calling another bean in same EJB-application if (optimize && isLocal()) { result = container.invoke(id, method, args, getTransaction(), getPrincipal(), getCredential()); } else { MarshalledObject mo = createMarshalledObject(id, method, args); // Invoke on the remote server, enforce marshaling if (isLocal()) { // ensure marshaling of exceptions is done properly try { result = container.invoke(mo).get(); } catch (Throwable e) { throw (Throwable)new MarshalledObject(e).get(); } } else { // Marshaling is done by RMI result = container.invoke(mo).get(); } } return result; } /** * Create a MarshalledObject suitable for * invoking a remote container with. * * @param id ??? * @param method The method to invoke. * @param args The arguments passed to the method. * @return MarshalledObject suitable for invoking * a remote container with. * * @throws SystemException Failed to get transaction. * @throws IOException Failed to create MarshalledObject. */ protected MarshalledObject createMarshalledObject(final Object id, final Method method, final Object[] args) throws SystemException, IOException { RemoteMethodInvocation rmi = new RemoteMethodInvocation(id, method, args); // Set the transaction propagation context rmi.setTransactionPropagationContext(getTransactionPropagationContext()); // Set the security stuff // MF fixme this will need to use "thread local" and therefore same construct as above // rmi.setPrincipal(sm != null? sm.getPrincipal() : null); // rmi.setCredential(sm != null? sm.getCredential() : null); // is the credential thread local? (don't think so... but...) rmi.setPrincipal(getPrincipal()); rmi.setCredential(getCredential()); return new MarshalledObject(rmi); } // Private ------------------------------------------------------- /** * Time of ContainerRemote class initialization as * read when this instance was created. This time is used to * determine if this instance lives in the same VM as the container. * * This is not completely fail-safe: If the ContainerRemote class * is initialized in the server and at the client within the same * millisecond, the proxy will think it is local, and the client * will fail. This is, however, very unlikely to happen in real * life, and next time the client is started it would run OK. */ private long containerStartup = ContainerRemote.STARTUP; // Inner classes ------------------------------------------------- }