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 }