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