Author: gawor Date: Thu Aug 19 17:04:24 2010 New Revision: 987233 URL: http://svn.apache.org/viewvc?rev=987233&view=rev Log: OPENEJB-1334: Throw EJBException when calling non-public methods of local bean proxy
Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImpl.java openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/BaseLocalBean.java openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImplTest.java openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/SampleLocalBean.java Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java?rev=987233&r1=987232&r2=987233&view=diff ============================================================================== --- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java (original) +++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java Thu Aug 19 17:04:24 2010 @@ -19,14 +19,20 @@ package org.apache.openejb.util.proxy; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.ejb.EJBException; public class LocalBeanProxyFactory { + private static final java.lang.reflect.InvocationHandler NON_BUSINESS_HANDLER = new NonBusinessHandler(); + public static Object newProxyInstance(ClassLoader cl, Class interfce, java.lang.reflect.InvocationHandler h) throws IllegalArgumentException { try { Class proxyCls = new LocalBeanProxyGeneratorImpl().createProxy(interfce, cl); - Constructor constructor = proxyCls.getConstructor(java.lang.reflect.InvocationHandler.class); - Object object = constructor.newInstance(h); + Constructor constructor = proxyCls.getConstructor(java.lang.reflect.InvocationHandler.class, + java.lang.reflect.InvocationHandler.class); + Object object = constructor.newInstance(h, NON_BUSINESS_HANDLER); return object; } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); @@ -41,7 +47,7 @@ public class LocalBeanProxyFactory { public static InvocationHandler getInvocationHandler(Object proxy) { try { - Field field = proxy.getClass().getDeclaredField("invocationHandler"); + Field field = proxy.getClass().getDeclaredField(LocalBeanProxyGeneratorImpl.BUSSINESS_HANDLER_NAME); field.setAccessible(true); try { return (InvocationHandler) field.get(proxy); @@ -54,4 +60,12 @@ public class LocalBeanProxyFactory { throw new IllegalArgumentException(e); } } + + private static class NonBusinessHandler implements java.lang.reflect.InvocationHandler { + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + throw new EJBException("Calling non-public methods of a local bean without any interfaces is not allowed"); + } + + } } Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImpl.java URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImpl.java?rev=987233&r1=987232&r2=987233&view=diff ============================================================================== --- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImpl.java (original) +++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImpl.java Thu Aug 19 17:04:24 2010 @@ -21,6 +21,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.xbean.asm.ClassWriter; import org.apache.xbean.asm.FieldVisitor; @@ -31,6 +36,9 @@ import org.apache.xbean.asm.Type; public class LocalBeanProxyGeneratorImpl implements LocalBeanProxyGenerator, Opcodes { + static final String BUSSINESS_HANDLER_NAME = "businessHandler"; + static final String NON_BUSINESS_HANDLER_NAME = "nonBusinessHandler"; + private static final sun.misc.Unsafe unsafe; static { @@ -84,38 +92,107 @@ public class LocalBeanProxyGeneratorImpl cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, proxyClassName, null, clsToOverride, null); cw.visitSource(clsToOverride + ".java", null); - // push InvocationHandler field - fv = cw.visitField(ACC_FINAL + ACC_PRIVATE, "invocationHandler", "Ljava/lang/reflect/InvocationHandler;", null, null); + // push InvocationHandler fields + fv = cw.visitField(ACC_FINAL + ACC_PRIVATE, BUSSINESS_HANDLER_NAME, "Ljava/lang/reflect/InvocationHandler;", null, null); + fv.visitEnd(); + fv = cw.visitField(ACC_FINAL + ACC_PRIVATE, NON_BUSINESS_HANDLER_NAME, "Ljava/lang/reflect/InvocationHandler;", null, null); fv.visitEnd(); - // push constructor + // push single argument constructor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKESPECIAL, proxyName, "<init>", "(Ljava/lang/reflect/InvocationHandler;Ljava/lang/reflect/InvocationHandler;)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // push double argument constructor + mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;Ljava/lang/reflect/InvocationHandler;)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, clsToOverride, "<init>", "()V"); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); - mv.visitFieldInsn(PUTFIELD, proxyName, "invocationHandler", "Ljava/lang/reflect/InvocationHandler;"); + mv.visitFieldInsn(PUTFIELD, proxyName, BUSSINESS_HANDLER_NAME, "Ljava/lang/reflect/InvocationHandler;"); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 2); + mv.visitFieldInsn(PUTFIELD, proxyName, NON_BUSINESS_HANDLER_NAME, "Ljava/lang/reflect/InvocationHandler;"); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); - // loop through public methods, and push something to the class - Method[] methods = clsToProxy.getMethods(); - for (Method method : methods) { - if (Modifier.isFinal(method.getModifiers())) { - // can't proxy final methods - continue; + Map<String, List<Method>> methodMap = getNonPrivateMethods(clsToProxy); + + for (Map.Entry<String, List<Method>> entry : methodMap.entrySet()) { + for (Method method : entry.getValue()) { + String name = method.getName(); + int modifiers = method.getModifiers(); + if (Modifier.isPublic(modifiers) || + (method.getParameterTypes().length == 0 && + ("finalize".equals(name) || "clone".equals(name)))) { + // forward invocations of any public methods or + // finalize/clone methods to businessHandler + processMethod(cw, method, proxyClassName, BUSSINESS_HANDLER_NAME); + } else { + // forward invocations of any other methods to nonBusinessHandler + processMethod(cw, method, proxyClassName, NON_BUSINESS_HANDLER_NAME); + } } - processMethod(cw, method, proxyClassName, clsToOverride); } byte[] clsBytes = cw.toByteArray(); return clsBytes; } + /* + * Return all protected, public, and default methods of a given class + * that are not final or static. The returned map includes the inherited methods + * and ensures that overridden methods are included once. + */ + private Map<String, List<Method>> getNonPrivateMethods(Class<?> clazz) { + Map<String, List<Method>> methodMap = new HashMap<String, List<Method>>(); + while (clazz != null) { + for (Method method : clazz.getDeclaredMethods()) { + int modifiers = method.getModifiers(); + if (Modifier.isFinal(modifiers) || + Modifier.isPrivate(modifiers) || + Modifier.isStatic(modifiers)) { + continue; + } + + List<Method> methods = methodMap.get(method.getName()); + if (methods == null) { + methods = new ArrayList<Method>(); + methods.add(method); + methodMap.put(method.getName(), methods); + } else { + if (isOverridden(methods, method)) { + // method is overridden in superclass, so do nothing + } else { + // method is not overridden, so add it + methods.add(method); + } + } + } + + clazz = clazz.getSuperclass(); + } + return methodMap; + } + + private boolean isOverridden(List<Method> methods, Method method) { + for (Method m : methods) { + if (Arrays.equals(m.getParameterTypes(), method.getParameterTypes())) { + return true; + } + } + return false; + } - private void processMethod(ClassWriter cw, Method method, String proxyName, String clsToOverride) throws ProxyGenerationException { + private void processMethod(ClassWriter cw, Method method, String proxyName, String handlerName) throws ProxyGenerationException { if ("<init>".equals(method.getName())) { return; } @@ -123,9 +200,16 @@ public class LocalBeanProxyGeneratorImpl Class<?> returnType = method.getReturnType(); Class<?>[] parameterTypes = method.getParameterTypes(); Class<?>[] exceptionTypes = method.getExceptionTypes(); + int modifiers = method.getModifiers(); // push the method definition - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName(), getMethodSignatureAsString(returnType, parameterTypes), null, null); + int modifier = 0; + if (Modifier.isPublic(modifiers)) { + modifier = ACC_PUBLIC; + } else if (Modifier.isProtected(modifiers)) { + modifier = ACC_PROTECTED; + } + MethodVisitor mv = cw.visitMethod(modifier, method.getName(), getMethodSignatureAsString(returnType, parameterTypes), null, null); mv.visitCode(); // push try/catch block, to catch declared exceptions, and to catch java.lang.Throwable @@ -142,6 +226,7 @@ public class LocalBeanProxyGeneratorImpl // push try code mv.visitLabel(l0); + String clsToOverride = method.getDeclaringClass().getName().replaceAll("\\.", "/"); mv.visitLdcInsn(Type.getType("L" + clsToOverride + ";")); // the following code generates the bytecode for this line of Java: @@ -182,7 +267,7 @@ public class LocalBeanProxyGeneratorImpl } // invoke getMethod() with the method name and the array of types - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); // store the returned method for later mv.visitVarInsn(ASTORE, length); @@ -195,7 +280,7 @@ public class LocalBeanProxyGeneratorImpl mv.visitVarInsn(ALOAD, 0); // get the invocationHandler field from this class - mv.visitFieldInsn(GETFIELD, proxyName, "invocationHandler", "Ljava/lang/reflect/InvocationHandler;"); + mv.visitFieldInsn(GETFIELD, proxyName, handlerName, "Ljava/lang/reflect/InvocationHandler;"); // we want to pass "this" in as the first parameter mv.visitVarInsn(ALOAD, 0); Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/BaseLocalBean.java URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/BaseLocalBean.java?rev=987233&r1=987232&r2=987233&view=diff ============================================================================== --- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/BaseLocalBean.java (original) +++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/BaseLocalBean.java Thu Aug 19 17:04:24 2010 @@ -25,4 +25,14 @@ public class BaseLocalBean { public final void finalMethod() { } + protected String protectedMethod() { + return "protected method"; + } + + protected String overriddenMethod() { + return getClass().getName(); + } + + public static void staticMethod() { + } } Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImplTest.java URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImplTest.java?rev=987233&r1=987232&r2=987233&view=diff ============================================================================== --- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImplTest.java (original) +++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/LocalBeanProxyGeneratorImplTest.java Thu Aug 19 17:04:24 2010 @@ -27,6 +27,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javax.ejb.EJBException; + public class LocalBeanProxyGeneratorImplTest extends TestCase { public class Call { @@ -174,9 +176,8 @@ public class LocalBeanProxyGeneratorImpl private SampleLocalBean loadProxy(TestInvocationHandler invocationHandler) throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); - - Class cls = new LocalBeanProxyGeneratorImpl().createProxy(SampleLocalBean.class, cl); - return (SampleLocalBean) cls.getConstructor(new Class[] { InvocationHandler.class }).newInstance(invocationHandler); + + return (SampleLocalBean) LocalBeanProxyFactory.newProxyInstance(cl, SampleLocalBean.class, invocationHandler); } public void testShouldReturnCorrectMethodSignatures() throws Exception { @@ -205,6 +206,26 @@ public class LocalBeanProxyGeneratorImpl } @Test + public void testNonPublicMethods() throws Exception { + TestInvocationHandler invocationHandler = new TestInvocationHandler(new SampleLocalBean()); + SampleLocalBean proxy = loadProxy(invocationHandler); + + try { + proxy.protectedMethod(); + fail("Protected method did not throw exception"); + } catch (EJBException e) { + // that's what we expect + } + + try { + proxy.defaultMethod(); + fail("Default method did not throw exception"); + } catch (EJBException e) { + // that's what we expect + } + } + + @Test public void testDoWork() throws Exception { TestInvocationHandler invocationHandler = new TestInvocationHandler(new SampleLocalBean()); SampleLocalBean proxy = loadProxy(invocationHandler); @@ -886,12 +907,16 @@ public class LocalBeanProxyGeneratorImpl public void testInheritedMethod() throws Exception { TestInvocationHandler invocationHandler = new TestInvocationHandler(new SampleLocalBean()); SampleLocalBean proxy = loadProxy(invocationHandler); - String result = proxy.hello("Bob"); + // call inherited method + String result = proxy.hello("Bob"); assertEquals("Hello Bob", result); assertEquals(1, invocationHandler.getCalls().length); Call call = invocationHandler.getCalls()[0]; assertEquals("hello", call.getMethodName()); + + // call overridden method + assertEquals(SampleLocalBean.class.getName(), proxy.overriddenMethod()); } public static class EnumParams { Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/SampleLocalBean.java URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/SampleLocalBean.java?rev=987233&r1=987232&r2=987233&view=diff ============================================================================== --- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/SampleLocalBean.java (original) +++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/util/proxy/SampleLocalBean.java Thu Aug 19 17:04:24 2010 @@ -24,11 +24,20 @@ import javax.ejb.LocalBean; @LocalBean public class SampleLocalBean extends BaseLocalBean { - + public SampleLocalBean() { super(); } + int defaultMethod() { + return -1; + } + + @Override + public String overriddenMethod() { + return getClass().getName(); + } + /* 1. void return, no arg */ public void doWork() { System.out.println("void doWork()");