Save This Page
Home » openejb-3.0-src » org.apache » openejb » core » ivm » [javadoc | source]
    1   /**
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.openejb.core.ivm;
   18   
   19   import java.io.ByteArrayInputStream;
   20   import java.io.ByteArrayOutputStream;
   21   import java.io.IOException;
   22   import java.io.ObjectInputStream;
   23   import java.io.ObjectOutputStream;
   24   import java.io.ObjectStreamException;
   25   import java.io.Serializable;
   26   import java.io.NotSerializableException;
   27   import java.lang.ref.WeakReference;
   28   import java.lang.reflect.Method;
   29   import java.math.BigDecimal;
   30   import java.rmi.NoSuchObjectException;
   31   import java.rmi.RemoteException;
   32   import java.rmi.AccessException;
   33   import java.util.HashSet;
   34   import java.util.Hashtable;
   35   import java.util.Properties;
   36   import java.util.List;
   37   import java.util.ArrayList;
   38   import java.util.WeakHashMap;
   39   import java.util.Set;
   40   
   41   import javax.ejb.EJBException;
   42   import javax.ejb.NoSuchObjectLocalException;
   43   import javax.ejb.TransactionRequiredLocalException;
   44   import javax.ejb.TransactionRolledbackLocalException;
   45   import javax.ejb.EJBTransactionRequiredException;
   46   import javax.ejb.EJBTransactionRolledbackException;
   47   import javax.ejb.NoSuchEJBException;
   48   import javax.ejb.AccessLocalException;
   49   import javax.transaction.TransactionRequiredException;
   50   import javax.transaction.TransactionRolledbackException;
   51   
   52   import org.apache.openejb.InterfaceType;
   53   import org.apache.openejb.RpcContainer;
   54   import org.apache.openejb.DeploymentInfo;
   55   import org.apache.openejb.core.CoreDeploymentInfo;
   56   import org.apache.openejb.loader.SystemInstance;
   57   import org.apache.openejb.spi.ContainerSystem;
   58   import org.apache.openejb.util.proxy.InvocationHandler;
   59   import org.apache.openejb.util.proxy.ProxyManager;
   60   
   61   public abstract class BaseEjbProxyHandler implements InvocationHandler, Serializable {
   62       private static final String OPENEJB_LOCALCOPY = "openejb.localcopy";
   63   
   64       private static class ProxyRegistry {
   65   
   66           protected final Hashtable liveHandleRegistry = new Hashtable();
   67       }
   68   
   69       public final Object deploymentID;
   70   
   71       public final Object primaryKey;
   72   
   73       public boolean inProxyMap = false;
   74   
   75       private transient WeakReference<CoreDeploymentInfo> deploymentInfo;
   76   
   77       public transient RpcContainer container;
   78   
   79       protected boolean isInvalidReference = false;
   80   
   81       /*
   82       * The EJB 1.1 specification requires that arguments and return values between beans adhere to the
   83       * Java RMI copy semantics which requires that the all arguments be passed by value (copied) and 
   84       * never passed as references.  However, it is possible for the system administrator to turn off the
   85       * copy operation so that arguments and return values are passed by reference as performance optimization.
   86       * Simply setting the org.apache.openejb.core.EnvProps.INTRA_VM_COPY property to FALSE will cause this variable to
   87       * set to false, and therefor bypass the copy operations in the invoke( ) method of this class; arguments
   88       * and return values will be passed by reference not value. 
   89       *
   90       * This property is, by default, always TRUE but it can be changed to FALSE by setting it as a System property
   91       * or a property of the Property argument when invoking OpenEJB.init(props).  This variable is set to that
   92       * property in the static block for this class.
   93       */
   94       protected boolean doIntraVmCopy;
   95       protected boolean doCrossClassLoaderCopy;
   96       private static final boolean REMOTE_COPY_ENABLED = parseRemoteCopySetting();
   97       protected final InterfaceType interfaceType;
   98       private transient WeakHashMap<Class,Object> interfaces;
   99       private transient WeakReference<Class> mainInterface;
  100   
  101       public BaseEjbProxyHandler(DeploymentInfo deploymentInfo, Object pk, InterfaceType interfaceType, List<Class> interfaces) {
  102           this.container = (RpcContainer) deploymentInfo.getContainer();
  103           this.deploymentID = deploymentInfo.getDeploymentID();
  104           this.interfaceType = interfaceType;
  105           this.primaryKey = pk;
  106           this.setDeploymentInfo((CoreDeploymentInfo) deploymentInfo);
  107   
  108           if (interfaces == null || interfaces.size() == 0) {
  109               InterfaceType objectInterfaceType = (interfaceType.isHome()) ? interfaceType.getCounterpart() : interfaceType;
  110               interfaces = new ArrayList<Class>(deploymentInfo.getInterfaces(objectInterfaceType));
  111           }
  112   
  113           this.doIntraVmCopy = !interfaceType.isLocal();
  114   
  115           if (!interfaceType.isLocal()){
  116               doIntraVmCopy = REMOTE_COPY_ENABLED;
  117           }
  118   
  119           setInterfaces(interfaces);
  120   
  121           if (interfaceType.isHome()){
  122               setMainInterface(deploymentInfo.getInterface(interfaceType));
  123           } else {
  124               // Then arbitrarily pick the first interface
  125               setMainInterface(interfaces.get(0));
  126           }
  127       }
  128   
  129       /**
  130        * This method should be called to determine the corresponding
  131        * business interface class to name as the invoking interface.
  132        * This method should NOT be called on non-business-interface
  133        * methods the proxy has such as java.lang.Object or IntraVmProxy.
  134        * @param method
  135        * @return the business (or component) interface matching this method
  136        */
  137       protected Class<?> getInvokedInterface(Method method) {
  138           // Home's only have one interface ever.  We don't
  139           // need to verify that the method invoked is in
  140           // it's interface.
  141           Class mainInterface = getMainInterface();
  142           if (interfaceType.isHome()) return mainInterface;
  143   
  144           Class declaringClass = method.getDeclaringClass();
  145   
  146           // If our "main" interface is or extends the method's declaring class
  147           // then we're good.  We know the main interface has the method being
  148           // invoked and it's safe to return it as the invoked interface.
  149           if (declaringClass.isAssignableFrom(mainInterface)){
  150               return mainInterface;
  151           }
  152   
  153           // If the method being invoked isn't in the "main" interface
  154           // we need to find a suitable interface or throw an exception.
  155           for (Class secondaryInterface : interfaces.keySet()) {
  156               if (declaringClass.isAssignableFrom(secondaryInterface)){
  157                   return secondaryInterface;
  158               }
  159           }
  160   
  161           // We couldn't find an implementing interface.  Where did this
  162           // method come from???  Freak occurence.  Throw an exception.
  163           throw new IllegalStateException("Received method invocation and cannot determine corresponding business interface: method=" + method);
  164       }
  165   
  166       public Class getMainInterface() {
  167           return mainInterface.get();
  168       }
  169   
  170       private void setMainInterface(Class referent) {
  171           mainInterface = new WeakReference<Class>(referent);
  172       }
  173   
  174       private void setInterfaces(List<Class> interfaces) {
  175           this.interfaces = new WeakHashMap<Class,Object>(interfaces.size());
  176           for (Class clazz : interfaces) {
  177               this.interfaces.put(clazz, null);
  178           }
  179       }
  180   
  181       public List<Class> getInterfaces() {
  182           Set<Class> classes = interfaces.keySet();
  183           return new ArrayList(classes);
  184       }
  185   
  186       private static boolean parseRemoteCopySetting() {
  187           Properties properties = SystemInstance.get().getProperties();
  188           String value = properties.getProperty(OPENEJB_LOCALCOPY);
  189           if (value == null) {
  190               value = properties.getProperty(org.apache.openejb.core.EnvProps.INTRA_VM_COPY);
  191           }
  192           return value == null || !value.equalsIgnoreCase("FALSE");
  193       }
  194   
  195       protected void checkAuthorization(Method method) throws org.apache.openejb.OpenEJBException {
  196       }
  197   
  198       public void setIntraVmCopyMode(boolean on) {
  199           doIntraVmCopy = on;
  200       }
  201   
  202       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  203           if (isInvalidReference) {
  204               if (interfaceType.isComponent() && interfaceType.isLocal()){
  205                   throw new NoSuchObjectLocalException("reference is invalid");
  206               } else if (interfaceType.isComponent() || java.rmi.Remote.class.isAssignableFrom(method.getDeclaringClass())) {
  207                   throw new NoSuchObjectException("reference is invalid");
  208               } else {
  209                   throw new javax.ejb.NoSuchEJBException("reference is invalid");
  210               }
  211           }
  212           getDeploymentInfo(); // will throw an exception if app has been undeployed.
  213   
  214           if (method.getDeclaringClass() == Object.class) {
  215               final String methodName = method.getName();
  216   
  217               if (methodName.equals("toString")) return toString();
  218               else if (methodName.equals("equals")) return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE;
  219               else if (methodName.equals("hashCode")) return new Integer(hashCode());
  220               else
  221                   throw new UnsupportedOperationException("Unkown method: " + method);
  222           } else if (method.getDeclaringClass() == IntraVmProxy.class) {
  223               final String methodName = method.getName();
  224   
  225               if (methodName.equals("writeReplace")) return _writeReplace(proxy);
  226               else
  227                   throw new UnsupportedOperationException("Unkown method: " + method);
  228           }
  229   
  230           Class interfce = getInvokedInterface(method);
  231   
  232           // Should we copy arguments as required by the specification?
  233           if (doIntraVmCopy && !doCrossClassLoaderCopy) {
  234   
  235               if (args != null && args.length > 0) {
  236                   IntraVmCopyMonitor.preCopyOperation();
  237                   try {
  238                       args = copyArgs(args);
  239                   } finally {
  240                       IntraVmCopyMonitor.postCopyOperation();
  241                   }
  242               }
  243               Object returnObj = null;
  244               try {
  245                   returnObj = _invoke(proxy, interfce, method, args);
  246               } catch (Throwable throwable) {
  247                   // exceptions are return values and must be coppied
  248                   IntraVmCopyMonitor.preCopyOperation();
  249                   try {
  250                       throwable = (Throwable) copyObj(throwable);
  251                       throw convertException(throwable, method, interfce);
  252                   } finally {
  253                       IntraVmCopyMonitor.postCopyOperation();
  254                   }
  255               }
  256   
  257               if (returnObj != null) {
  258                   IntraVmCopyMonitor.preCopyOperation();
  259                   try {
  260                       returnObj = copyObj(returnObj);
  261                   } finally {
  262                       IntraVmCopyMonitor.postCopyOperation();
  263                   }
  264               }
  265               return returnObj;
  266   
  267           } else if (doIntraVmCopy) {
  268               // copy method and arguments to EJB's class loader
  269               IntraVmCopyMonitor.preCrossClassLoaderOperation();
  270               ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
  271               Thread.currentThread().setContextClassLoader(getDeploymentInfo().getClassLoader());
  272               try {
  273                   if (args != null && args.length > 0) {
  274                       args = copyArgs(args);
  275                   }
  276                   method = copyMethod(method);
  277                   interfce = (Class) copyObj(interfce);
  278               } finally {
  279                   Thread.currentThread().setContextClassLoader(oldClassLoader);
  280                   IntraVmCopyMonitor.postCrossClassLoaderOperation();
  281               }
  282   
  283               // invoke method
  284               Object returnObj = null;
  285               try {
  286                   returnObj = _invoke(proxy, interfce, method, args);
  287               } catch (Throwable throwable) {
  288                   // exceptions are return values and must be coppied
  289                   IntraVmCopyMonitor.preCrossClassLoaderOperation();
  290                   try {
  291                       throwable = (Throwable) copyObj(throwable);
  292                       throw convertException(throwable, method, interfce);
  293                   } finally {
  294                       IntraVmCopyMonitor.postCrossClassLoaderOperation();
  295                   }
  296               }
  297   
  298               if (returnObj != null) {
  299                   IntraVmCopyMonitor.preCrossClassLoaderOperation();
  300                   try {
  301                       returnObj = copyObj(returnObj);
  302                   } finally {
  303                       IntraVmCopyMonitor.postCrossClassLoaderOperation();
  304                   }
  305               }
  306               return returnObj;
  307           } else {
  308               try {
  309                   /*
  310                        * The EJB 1.1 specification requires that arguments and return values between beans adhere to the
  311                        * Java RMI copy semantics which requires that the all arguments be passed by value (copied) and
  312                        * never passed as references.  However, it is possible for the system administrator to turn off the
  313                        * copy operation so that arguments and return values are passed by reference as a performance optimization.
  314                        * Simply setting the org.apache.openejb.core.EnvProps.INTRA_VM_COPY property to FALSE will cause
  315                        * IntraVM to bypass the copy operations; arguments and return values will be passed by reference not value.
  316                        * This property is, by default, always TRUE but it can be changed to FALSE by setting it as a System property
  317                        * or a property of the Property argument when invoking OpenEJB.init(props).  The doIntraVmCopy variable is set to that
  318                        * property in the static block for this class.
  319                        */
  320   
  321                   return _invoke(proxy, interfce, method, args);
  322               } catch (Throwable t) {
  323                   throw convertException(t, method, interfce);
  324               }
  325           }
  326       }
  327   
  328       /**
  329        * Renamed method so it shows up with a much more understandable purpose as it
  330        * will be the top element in the stacktrace
  331        * @param e
  332        * @param method
  333        * @param interfce
  334        */
  335       protected Throwable convertException(Throwable e, Method method, Class interfce) {
  336           boolean rmiRemote = java.rmi.Remote.class.isAssignableFrom(interfce);
  337           if (e instanceof TransactionRequiredException) {
  338               if (!rmiRemote && interfaceType.isBusiness()) {
  339                   return new EJBTransactionRequiredException(e.getMessage()).initCause(getCause(e));
  340               } else if (interfaceType.isLocal()) {
  341                   return new TransactionRequiredLocalException(e.getMessage()).initCause(getCause(e));
  342               } else {
  343                   return e;
  344               }
  345           }
  346           if (e instanceof TransactionRolledbackException) {
  347               if (!rmiRemote && interfaceType.isBusiness()) {
  348                   return new EJBTransactionRolledbackException(e.getMessage()).initCause(getCause(e));
  349               } else if (interfaceType.isLocal()) {
  350                   return new TransactionRolledbackLocalException(e.getMessage()).initCause(getCause(e));
  351               } else {
  352                   return e;
  353               }
  354           }
  355           if (e instanceof NoSuchObjectException) {
  356               if (!rmiRemote && interfaceType.isBusiness()) {
  357                   return new NoSuchEJBException(e.getMessage()).initCause(getCause(e));
  358               } else if (interfaceType.isLocal()) {
  359                   return new NoSuchObjectLocalException(e.getMessage()).initCause(getCause(e));
  360               } else {
  361                   return e;
  362               }
  363           }
  364           if (e instanceof RemoteException) {
  365               if (!rmiRemote && interfaceType.isBusiness()) {
  366                   return new EJBException(e.getMessage()).initCause(getCause(e));
  367               } else if (interfaceType.isLocal()) {
  368                   return new EJBException(e.getMessage()).initCause(getCause(e));
  369               } else {
  370                   return e;
  371               }
  372           }
  373           if (e instanceof AccessException) {
  374               if (!rmiRemote && interfaceType.isBusiness()) {
  375                   return new AccessLocalException(e.getMessage()).initCause(getCause(e));
  376               } else if (interfaceType.isLocal()) {
  377                   return new AccessLocalException(e.getMessage()).initCause(getCause(e));
  378               } else {
  379                   return e;
  380               }
  381           }
  382   
  383           for (Class<?> type : method.getExceptionTypes()) {
  384               if (type.isAssignableFrom(e.getClass())) {
  385                   return e;
  386               }
  387           }
  388   
  389           // Exception is undeclared
  390           // Try and find a runtime exception in there
  391           while (e.getCause() != null && !(e instanceof RuntimeException)) {
  392               e = e.getCause();
  393           }
  394           return e;
  395       }
  396   
  397       /**
  398        * Method instance on proxies that come from a classloader outside
  399        * the bean's classloader need to be swapped out for the identical
  400        * method in the bean's classloader.
  401        *
  402        * @param method
  403        * @return return's the same method but loaded from the beans classloader
  404        */
  405   
  406       private Method copyMethod(Method method) throws Exception {
  407           int parameterCount = method.getParameterTypes().length;
  408           Object[] types = new Object[1 + parameterCount];
  409           types[0] = method.getDeclaringClass();
  410           System.arraycopy(method.getParameterTypes(), 0, types, 1, parameterCount);
  411   
  412           types = copyArgs(types);
  413   
  414           Class targetClass = (Class) types[0];
  415           Class[] targetParameters = new Class[parameterCount];
  416           System.arraycopy(types, 1, targetParameters, 0, parameterCount);
  417           Method targetMethod = targetClass.getMethod(method.getName(), targetParameters);
  418           return targetMethod;
  419       }
  420   
  421       protected Throwable getCause(Throwable e) {
  422           if (e != null && e.getCause() != null) {
  423               return e.getCause();
  424           }
  425           return e;
  426       }
  427   
  428       public String toString() {
  429           String name = null;
  430           try {
  431               name = getProxyInfo().getInterface().getName();
  432           } catch (Exception e) {
  433           }
  434           return "proxy=" + name + ";deployment=" + this.deploymentID + ";pk=" + this.primaryKey;
  435       }
  436   
  437       public int hashCode() {
  438           if (primaryKey == null) {
  439   
  440               return deploymentID.hashCode();
  441           } else {
  442               return primaryKey.hashCode();
  443           }
  444       }
  445   
  446       public boolean equals(Object obj) {
  447           try {
  448               obj = ProxyManager.getInvocationHandler(obj);
  449           } catch (IllegalArgumentException e) {
  450               return false;
  451           }
  452           BaseEjbProxyHandler other = (BaseEjbProxyHandler) obj;
  453           if (primaryKey == null) {
  454               return other.primaryKey == null && deploymentID.equals(other.deploymentID);
  455           } else {
  456               return primaryKey.equals(other.primaryKey) && deploymentID.equals(other.deploymentID);
  457           }
  458       }
  459   
  460       protected abstract Object _invoke(Object proxy, Class interfce, Method method, Object[] args) throws Throwable;
  461   
  462       protected Object[] copyArgs(Object[] objects) throws IOException, ClassNotFoundException {
  463           /* 
  464               while copying the arguments is necessary. Its not necessary to copy the array itself,
  465               because they array is created by the Proxy implementation for the sole purpose of 
  466               packaging the arguments for the InvocationHandler.invoke( ) method. Its ephemeral
  467               and their for doesn't need to be copied.
  468           */
  469   
  470           for (int i = 0; i < objects.length; i++) {
  471               objects[i] = copyObj(objects[i]);
  472           }
  473   
  474           return objects;
  475       }
  476   
  477       /* change dereference to copy */
  478       protected Object copyObj(Object object) throws IOException, ClassNotFoundException {
  479       	// Check for primitive and other known class types that are immutable.  If detected
  480       	// we can safely return them.
  481       	if (object == null) return null;
  482       	Class ooc = object.getClass();
  483           if ((ooc == int.class         ) ||
  484               (ooc == String.class      ) ||
  485               (ooc == long.class        ) ||
  486               (ooc == boolean.class     ) ||
  487               (ooc == byte.class        ) ||
  488               (ooc == float.class       ) ||
  489               (ooc == double.class      ) ||
  490               (ooc == short.class       ) ||
  491               (ooc == Long.class        ) ||
  492               (ooc == Boolean.class     ) ||
  493               (ooc == Byte.class        ) ||
  494               (ooc == Character.class   ) ||
  495               (ooc == Float.class       ) ||
  496               (ooc == Double.class      ) ||
  497               (ooc == Short.class       ) ||
  498               (ooc == BigDecimal.class  ))
  499           {
  500               return object;
  501           }
  502   
  503   
  504           ByteArrayOutputStream baos = null;
  505           try {
  506               baos = new ByteArrayOutputStream(128);
  507               ObjectOutputStream out = new ObjectOutputStream(baos);
  508               out.writeObject(object);
  509               out.close();
  510           } catch (NotSerializableException e) {
  511               throw (IOException) new NotSerializableException(e.getMessage()+" : The EJB specification restricts remote interfaces to only serializable data types.  This can be disabled for in-vm use with the "+OPENEJB_LOCALCOPY+"=false system property.").initCause(e);
  512           }
  513   
  514           ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  515           ObjectInputStream in = new EjbObjectInputStream(bais);
  516           Object obj = in.readObject();
  517           return obj;
  518       }
  519   
  520       public void invalidateReference() {
  521           this.container = null;
  522           this.setDeploymentInfo(null);
  523           this.isInvalidReference = true;
  524       }
  525   
  526       protected void invalidateAllHandlers(Object key) {
  527           HashSet<BaseEjbProxyHandler> set = (HashSet) getLiveHandleRegistry().remove(key);
  528           if (set == null) return;
  529           synchronized (set) {
  530               for (BaseEjbProxyHandler handler : set) {
  531                   handler.invalidateReference();
  532               }
  533           }
  534       }
  535   
  536       protected abstract Object _writeReplace(Object proxy) throws ObjectStreamException;
  537   
  538       protected void registerHandler(Object key, BaseEjbProxyHandler handler) {
  539           HashSet set = (HashSet) getLiveHandleRegistry().get(key);
  540           if (set != null) {
  541               synchronized (set) {
  542                   set.add(handler);
  543               }
  544           } else {
  545               set = new HashSet();
  546               set.add(handler);
  547               getLiveHandleRegistry().put(key, set);
  548           }
  549       }
  550   
  551       public abstract org.apache.openejb.ProxyInfo getProxyInfo();
  552   
  553       public CoreDeploymentInfo getDeploymentInfo() {
  554           CoreDeploymentInfo info = deploymentInfo.get();
  555           if (info == null|| info.isDestroyed()){
  556               invalidateReference();
  557               throw new IllegalStateException("Bean '"+deploymentID+"' has been undeployed.");
  558           }
  559           return info;
  560       }
  561   
  562       public void setDeploymentInfo(CoreDeploymentInfo deploymentInfo) {
  563           this.deploymentInfo = new WeakReference<CoreDeploymentInfo>(deploymentInfo);
  564       }
  565   
  566       public Hashtable getLiveHandleRegistry() {
  567           CoreDeploymentInfo deploymentInfo = getDeploymentInfo();
  568           ProxyRegistry proxyRegistry = deploymentInfo.get(ProxyRegistry.class);
  569           if (proxyRegistry == null){
  570               proxyRegistry = new ProxyRegistry();
  571               deploymentInfo.set(ProxyRegistry.class, proxyRegistry);
  572           }
  573           return proxyRegistry.liveHandleRegistry;
  574       }
  575   
  576       private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  577           out.defaultWriteObject();
  578   
  579           out.writeObject(getInterfaces());
  580           out.writeObject(getMainInterface());
  581       }
  582   
  583       private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
  584   
  585           in.defaultReadObject();
  586   
  587           ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class);
  588           setDeploymentInfo((CoreDeploymentInfo) containerSystem.getDeploymentInfo(deploymentID));
  589           container = (RpcContainer) getDeploymentInfo().getContainer();
  590   
  591           if (IntraVmCopyMonitor.isCrossClassLoaderOperation()) {
  592               doCrossClassLoaderCopy = true;
  593           }
  594   
  595           setInterfaces((List<Class>) in.readObject());
  596           setMainInterface((Class) in.readObject());
  597       }
  598   
  599   }

Save This Page
Home » openejb-3.0-src » org.apache » openejb » core » ivm » [javadoc | source]