Author: dblevins Date: Sat Sep 21 10:32:17 2013 New Revision: 1525217 URL: http://svn.apache.org/r1525217 Log: TOMEE-1040 - Abstract Dynamic Beans
Added: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/ tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/DynamicSubclass.java (with props) tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ColorsBefore.java (with props) tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSingletonTest.java (with props) tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatefulTest.java (with props) tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatelessTest.java (with props) tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSubclassTest.java (with props) tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/DynamicSubclassEjbDeploymentTest.java - copied, changed from r1524933, tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EjbDeploymentTest.java Modified: tomee/tomee/trunk/container/openejb-api/src/main/java/org/apache/openejb/api/Proxy.java tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanBuilder.java tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/rules/CheckClasses.java tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java Modified: tomee/tomee/trunk/container/openejb-api/src/main/java/org/apache/openejb/api/Proxy.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-api/src/main/java/org/apache/openejb/api/Proxy.java?rev=1525217&r1=1525216&r2=1525217&view=diff ============================================================================== --- tomee/tomee/trunk/container/openejb-api/src/main/java/org/apache/openejb/api/Proxy.java (original) +++ tomee/tomee/trunk/container/openejb-api/src/main/java/org/apache/openejb/api/Proxy.java Sat Sep 21 10:32:17 2013 @@ -20,11 +20,12 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.InvocationHandler; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Proxy { - Class<?> value(); + Class<? extends InvocationHandler> value(); } Modified: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanBuilder.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanBuilder.java?rev=1525217&r1=1525216&r2=1525217&view=diff ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanBuilder.java (original) +++ tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanBuilder.java Sat Sep 21 10:32:17 2013 @@ -22,6 +22,7 @@ import org.apache.openejb.Injection; import org.apache.openejb.ModuleContext; import org.apache.openejb.OpenEJBException; import org.apache.openejb.core.cmp.CmpUtil; +import org.apache.openejb.dyni.DynamicSubclass; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.util.Duration; @@ -78,6 +79,10 @@ class EnterpriseBeanBuilder { public BeanContext build() throws OpenEJBException { Class ejbClass = loadClass(bean.ejbClass, "classNotFound.ejbClass"); + if (DynamicSubclass.isDynamic(ejbClass)) { + ejbClass = DynamicSubclass.createSubclass(ejbClass, moduleContext.getClassLoader()); + } + Class home = null; Class remote = null; if (bean.home != null) { Modified: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java?rev=1525217&r1=1525216&r2=1525217&view=diff ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java (original) +++ tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java Sat Sep 21 10:32:17 2013 @@ -22,8 +22,10 @@ import org.apache.openejb.api.LocalClien import org.apache.openejb.api.Proxy; import org.apache.openejb.api.RemoteClient; import org.apache.openejb.cdi.CdiBeanInfo; +import org.apache.openejb.config.rules.CheckClasses; import org.apache.openejb.core.EmptyResourcesClassLoader; import org.apache.openejb.core.webservices.JaxWsUtils; +import org.apache.openejb.dyni.DynamicSubclass; import org.apache.openejb.jee.ActivationConfig; import org.apache.openejb.jee.ActivationSpec; import org.apache.openejb.jee.AdminObject; @@ -216,6 +218,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.MalformedURLException; @@ -1736,17 +1739,16 @@ public class AnnotationDeployer implemen } } - // not a dynamic proxy implemented bean - if (beanClass.getAnnotation(PersistenceContext.class) == null - && beanClass.getAnnotation(Proxy.class) == null - && beanClass.get().isInterface()) { - ejbModule.getValidation().fail(ejbName, "interfaceAnnotatedAsBean", annotationClass.getSimpleName(), beanClass.get().getName()); - return false; - } - - if (!beanClass.get().isInterface() && isAbstract(beanClass.get().getModifiers())) { - ejbModule.getValidation().fail(ejbName, "abstractAnnotatedAsBean", annotationClass.getSimpleName(), beanClass.get().getName()); - return false; + if (beanClass.get().isInterface()) { + if (!CheckClasses.isAbstractAllowed(beanClass.get())) { + ejbModule.getValidation().fail(ejbName, "interfaceAnnotatedAsBean", annotationClass.getSimpleName(), beanClass.get().getName()); + return false; + } + } else if (isAbstract(beanClass.get().getModifiers())) { + if (!CheckClasses.isAbstractAllowed(beanClass.get())) { + ejbModule.getValidation().fail(ejbName, "abstractAnnotatedAsBean", annotationClass.getSimpleName(), beanClass.get().getName()); + return false; + } } return b; @@ -2890,6 +2892,7 @@ public class AnnotationDeployer implemen !name.equals("groovy.lang.GroovyObject") && !name.equals("java.io.Serializable") && !name.equals("java.io.Externalizable") && + !(name.equals(InvocationHandler.class.getName()) && DynamicSubclass.isDynamic(beanClass)) && !name.startsWith("javax.ejb.") && !descriptor.contains(interfce.getName()) && !interfce.isSynthetic() && Modified: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/rules/CheckClasses.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/rules/CheckClasses.java?rev=1525217&r1=1525216&r2=1525217&view=diff ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/rules/CheckClasses.java (original) +++ tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/config/rules/CheckClasses.java Sat Sep 21 10:32:17 2013 @@ -19,6 +19,7 @@ package org.apache.openejb.config.rules; import org.apache.openejb.OpenEJBException; import org.apache.openejb.OpenEJBRuntimeException; import org.apache.openejb.config.EjbModule; +import org.apache.openejb.dyni.DynamicSubclass; import org.apache.openejb.jee.EnterpriseBean; import org.apache.openejb.jee.EntityBean; import org.apache.openejb.jee.Interceptor; @@ -230,13 +231,19 @@ public class CheckClasses extends Valida if (isCmp(b)) return beanClass; - if (isAbstract(beanClass.getModifiers()) && !isDynamicProxyImpl){ + if (isAbstract(beanClass.getModifiers()) && !isAbstractAllowed(beanClass)){ fail(ejbName, "abstractDeclaredAsBean", beanClass.getName()); } return beanClass; } + public static boolean isAbstractAllowed(Class clazz) { + if (DynamicProxyImplFactory.isKnownDynamicallyImplemented(clazz)) return true; + if (DynamicSubclass.isDynamic(clazz)) return true; + return false; + } + private void check_hasInterceptorClass(Interceptor i) { lookForClass(i.getInterceptorClass(), "interceptor-class", "Interceptor"); Added: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/DynamicSubclass.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/DynamicSubclass.java?rev=1525217&view=auto ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/DynamicSubclass.java (added) +++ tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/DynamicSubclass.java Sat Sep 21 10:32:17 2013 @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.dyni; + +import org.apache.openejb.loader.IO; +import org.apache.openejb.util.Debug; +import org.apache.openejb.util.proxy.LocalBeanProxyFactory; +import org.apache.openejb.util.proxy.ProxyGenerationException; +import org.apache.xbean.asm4.AnnotationVisitor; +import org.apache.xbean.asm4.ClassReader; +import org.apache.xbean.asm4.ClassVisitor; +import org.apache.xbean.asm4.ClassWriter; +import org.apache.xbean.asm4.MethodVisitor; +import org.apache.xbean.asm4.Opcodes; +import org.apache.xbean.asm4.Type; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @version $Rev$ $Date$ + */ +public class DynamicSubclass implements Opcodes { + + private static final ReentrantLock LOCK = new ReentrantLock(); + + public static boolean isDynamic(Class beanClass) { + return Modifier.isAbstract(beanClass.getModifiers()) && InvocationHandler.class.isAssignableFrom(beanClass); + } + + public static Class createSubclass(final Class<?> abstractClass, final ClassLoader cl) { + final String proxyName = getSubclassName(abstractClass); + + try { + return cl.loadClass(proxyName); + } catch (Exception e) { + // no-op + } + + final ReentrantLock lock = LOCK; + lock.lock(); + + try { + + try { // Try it again, another thread may have beaten this one... + return cl.loadClass(proxyName); + } catch (Exception e) { + // no-op + } + + final byte[] bytes = generateBytes(abstractClass); + return LocalBeanProxyFactory.Unsafe.defineClass(abstractClass, proxyName, bytes); + + } catch (Exception e) { + throw new InternalError(DynamicSubclass.class.getSimpleName() + ".createSubclass: " + Debug.printStackTrace(e)); + } finally { + lock.unlock(); + } + } + + private static byte[] generateBytes(final Class<?> classToProxy) throws ProxyGenerationException { + + final Map<String, MethodVisitor> visitors = new HashMap<String, MethodVisitor>(); + + final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + final String proxyClassFileName = getSubclassName(classToProxy).replace('.', '/'); + final String classFileName = classToProxy.getName().replace('.', '/'); + + cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, proxyClassFileName, null, classFileName, null); + cw.visitSource(classFileName + ".java", null); + + + // push InvocationHandler field + cw.visitField(ACC_FINAL + ACC_PRIVATE, "this$handler", "Ljava/lang/reflect/InvocationHandler;", null, null).visitEnd(); + + for (Constructor<?> constructor : classToProxy.getConstructors()) { + if (!Modifier.isPublic(constructor.getModifiers())) continue; + + final MethodVisitor mv = visitConstructor(cw, proxyClassFileName, classFileName, constructor); + visitors.put("<init>" + Type.getConstructorDescriptor(constructor), mv); + } + + final Map<String, List<Method>> methodMap = new HashMap<String, List<Method>>(); + + getNonPrivateMethods(classToProxy, methodMap); + + // Iterate over the public methods + for (final Map.Entry<String, List<Method>> entry : methodMap.entrySet()) { + + for (final Method method : entry.getValue()) { + if (Modifier.isAbstract(method.getModifiers())) { + final MethodVisitor visitor = LocalBeanProxyFactory.visit(cw, method, proxyClassFileName, "this$handler"); + visitors.put(method.getName() + Type.getMethodDescriptor(method), visitor); + } + } + } + + copyClassAnnotations(classToProxy, cw); + + copyMethodAnnotations(classToProxy, visitors); + + // This should never be reached, but just in case + for (MethodVisitor visitor : visitors.values()) { + visitor.visitEnd(); + } + + return cw.toByteArray(); + } + + private static MethodVisitor visitConstructor(ClassWriter cw, String proxyClassFileName, String classFileName, Constructor<?> constructor) { + final String descriptor = Type.getConstructorDescriptor(constructor); + + final String[] exceptions = new String[constructor.getExceptionTypes().length]; + for (int i = 0; i < exceptions.length; i++) { + exceptions[i] = Type.getInternalName(constructor.getExceptionTypes()[i]); + } + + final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", descriptor, null, exceptions); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + int index = 1; + + for (Type type : Type.getArgumentTypes(descriptor)) { + mv.visitVarInsn(type.getOpcode(ILOAD), index); + index += size(type); + } + + mv.visitMethodInsn(INVOKESPECIAL, classFileName, "<init>", descriptor); + + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(PUTFIELD, proxyClassFileName, "this$handler", "Ljava/lang/reflect/InvocationHandler;"); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 1); + return mv; + } + + private static String getSubclassName(Class<?> classToProxy) { + return classToProxy.getName() + "$$Impl"; + } + + private static void getNonPrivateMethods(Class<?> clazz, final Map<String, List<Method>> methodMap) { + while (clazz != null) { + for (final Method method : clazz.getDeclaredMethods()) { + final 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(); + } + } + + private static boolean isOverridden(final List<Method> methods, final Method method) { + for (final Method m : methods) { + if (Arrays.equals(m.getParameterTypes(), method.getParameterTypes())) { + return true; + } + } + return false; + } + + public static int size(Type type) { + if (Type.VOID_TYPE.equals(type)) return 0; + if (Type.LONG_TYPE.equals(type) || Type.DOUBLE_TYPE.equals(type)) return 2; + return 1; + } + + public static byte[] readClassFile(Class clazz) throws IOException { + return readClassFile(clazz.getClassLoader(), clazz); + } + + public static byte[] readClassFile(ClassLoader classLoader, Class clazz) throws IOException { + final String internalName = clazz.getName().replace('.', '/') + ".class"; + final URL resource = classLoader.getResource(internalName); + + final InputStream in = IO.read(resource); + final ByteArrayOutputStream out; + try { + out = new ByteArrayOutputStream(); + IO.copy(in, out); + } finally { + IO.close(in); + } + + return out.toByteArray(); + } + + private static void copyMethodAnnotations(Class<?> classToProxy, Map<String, MethodVisitor> visitors) throws ProxyGenerationException { + // Move all the annotations onto the newly implemented methods + // Ensures CDI and JAX-RS and JAX-WS still work + Class clazz = classToProxy; + while (clazz != null && !clazz.equals(Object.class)) { + try { + final ClassReader classReader = new ClassReader(readClassFile(clazz)); + final ClassVisitor copyMethodAnnotations = new CopyMethodAnnotations(visitors); + classReader.accept(copyMethodAnnotations, ClassReader.SKIP_CODE); + } catch (IOException e) { + throw new ProxyGenerationException(e); + } + clazz = clazz.getSuperclass(); + } + } + + private static void copyClassAnnotations(Class<?> clazz, final ClassVisitor newClass) throws ProxyGenerationException { + try { + final ClassReader classReader = new ClassReader(readClassFile(clazz)); + final ClassVisitor visitor = new CopyClassAnnotations(newClass); + classReader.accept(visitor, ClassReader.SKIP_CODE); + } catch (IOException e) { + throw new ProxyGenerationException(e); + } + } + + + public static class MoveAnnotationsVisitor extends MethodVisitor { + + private MethodVisitor newMethod; + + public MoveAnnotationsVisitor(MethodVisitor movedMethod, MethodVisitor newMethod) { + super(Opcodes.ASM4, movedMethod); + this.newMethod = newMethod; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return newMethod.visitAnnotation(desc, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + return newMethod.visitParameterAnnotation(parameter, desc, visible); + } + + @Override + public void visitEnd() { + newMethod.visitEnd(); + super.visitEnd(); + } + } + + + private static class CopyClassAnnotations extends ClassVisitor { + private final ClassVisitor newClass; + + public CopyClassAnnotations(ClassVisitor newClass) { + super(Opcodes.ASM4); + this.newClass = newClass; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return newClass.visitAnnotation(desc, visible); + } + } + + private static class CopyMethodAnnotations extends ClassVisitor { + private final Map<String, MethodVisitor> visitors; + + public CopyMethodAnnotations(Map<String, MethodVisitor> visitors) { + super(Opcodes.ASM4); + this.visitors = visitors; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + final MethodVisitor newMethod = visitors.remove(name + desc); + + if (newMethod == null) return null; + + final MethodVisitor oldMethod = super.visitMethod(access, name, desc, signature, exceptions); + + return new MoveAnnotationsVisitor(oldMethod, newMethod); + } + } +} Propchange: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/dyni/DynamicSubclass.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java?rev=1525217&r1=1525216&r2=1525217&view=diff ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java (original) +++ tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/util/proxy/LocalBeanProxyFactory.java Sat Sep 21 10:32:17 2013 @@ -241,11 +241,15 @@ public class LocalBeanProxyFactory imple return false; } - private static void processMethod(final ClassWriter cw, final Method method, final String proxyName, final String handlerName) throws ProxyGenerationException { + public static void processMethod(final ClassWriter cw, final Method method, final String proxyName, final String handlerName) throws ProxyGenerationException { if ("<init>".equals(method.getName())) { return; } + visit(cw, method, proxyName, handlerName).visitEnd(); + } + + public static MethodVisitor visit(ClassWriter cw, Method method, String proxyName, String handlerName) throws ProxyGenerationException { final Class<?> returnType = method.getReturnType(); final Class<?>[] parameterTypes = method.getParameterTypes(); final Class<?>[] exceptionTypes = method.getExceptionTypes(); @@ -431,7 +435,7 @@ public class LocalBeanProxyFactory imple // finish this method mv.visitMaxs(0, 0); - mv.visitEnd(); + return mv; } /** @@ -694,7 +698,7 @@ public class LocalBeanProxyFactory imple /** * The methods of this class model sun.misc.Unsafe which is used reflectively */ - private static class Unsafe { + public static class Unsafe { // sun.misc.Unsafe private static final Object unsafe; @@ -812,7 +816,7 @@ public class LocalBeanProxyFactory imple } } - private static Class defineClass(final Class<?> clsToProxy, final String proxyName, final byte[] proxyBytes) throws IllegalAccessException, InvocationTargetException { + public static Class defineClass(final Class<?> clsToProxy, final String proxyName, final byte[] proxyBytes) throws IllegalAccessException, InvocationTargetException { return (Class<?>) defineClass.invoke(unsafe, proxyName, proxyBytes, 0, proxyBytes.length, clsToProxy.getClassLoader(), clsToProxy.getProtectionDomain()); } } Added: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ColorsBefore.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ColorsBefore.java?rev=1525217&view=auto ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ColorsBefore.java (added) +++ tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ColorsBefore.java Sat Sep 21 10:32:17 2013 @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.dyni; + +/** + * @version $Rev$ $Date$ + */ +public class ColorsBefore { +} Propchange: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/ColorsBefore.java ------------------------------------------------------------------------------ svn:eol-style = native Added: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSingletonTest.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSingletonTest.java?rev=1525217&view=auto ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSingletonTest.java (added) +++ tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSingletonTest.java Sat Sep 21 10:32:17 2013 @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.dyni; + +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.Module; +import org.apache.openejb.util.Join; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.ejb.EJB; +import javax.ejb.Singleton; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + + +/** + * @version $Rev$ $Date$ + */ +@RunWith(ApplicationComposer.class) +public class DynamicSingletonTest extends Assert { + + @EJB + private Colors colors; + + @Module + public Class[] dynamic() { + return new Class[]{Colors.class}; + } + + @Test + public void test() throws Exception { + + assertNotNull(colors); + assertEquals("red", colors.red()); + assertEquals("handle:blue(hello)", colors.blue("hello")); + assertEquals("handle:green()", colors.green()); + } + + + @Singleton + public static abstract class Colors implements InvocationHandler { + + public String red() { + return "red"; + } + + public abstract String green(); + + public abstract String blue(String s); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return "handle:" + method.getName() + "(" + Join.join(",", args) + ")"; + } + } +} Propchange: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSingletonTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatefulTest.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatefulTest.java?rev=1525217&view=auto ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatefulTest.java (added) +++ tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatefulTest.java Sat Sep 21 10:32:17 2013 @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.dyni; + +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.Module; +import org.apache.openejb.util.Join; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.ejb.EJB; +import javax.ejb.Stateful; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + + +/** + * @version $Rev$ $Date$ + */ +@RunWith(ApplicationComposer.class) +public class DynamicStatefulTest extends Assert { + + @EJB + private Colors colors; + + @Module + public Class[] dynamic() { + return new Class[]{Colors.class}; + } + + @Test + public void test() throws Exception { + + assertNotNull(colors); + assertEquals("red", colors.red()); + assertEquals("handle:blue(hello)", colors.blue("hello")); + assertEquals("handle:green()", colors.green()); + } + + + @Stateful + public static abstract class Colors implements InvocationHandler { + + public String red() { + return "red"; + } + + public abstract String green(); + + public abstract String blue(String s); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return "handle:" + method.getName() + "(" + Join.join(",", args) + ")"; + } + } +} Propchange: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatefulTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatelessTest.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatelessTest.java?rev=1525217&view=auto ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatelessTest.java (added) +++ tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatelessTest.java Sat Sep 21 10:32:17 2013 @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.dyni; + +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.Module; +import org.apache.openejb.util.Join; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.ejb.EJB; +import javax.ejb.Singleton; +import javax.ejb.Stateless; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + + +/** + * @version $Rev$ $Date$ + */ +@RunWith(ApplicationComposer.class) +public class DynamicStatelessTest extends Assert { + + @EJB + private Colors colors; + + @Module + public Class[] dynamic() { + return new Class[]{Colors.class}; + } + + @Test + public void test() throws Exception { + + assertNotNull(colors); + assertEquals("red", colors.red()); + assertEquals("handle:blue(hello)", colors.blue("hello")); + assertEquals("handle:green()", colors.green()); + } + + + @Stateless + public static abstract class Colors implements InvocationHandler { + + public String red() { + return "red"; + } + + public abstract String green(); + + public abstract String blue(String s); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return "handle:" + method.getName() + "(" + Join.join(",", args) + ")"; + } + } +} Propchange: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicStatelessTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSubclassTest.java URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSubclassTest.java?rev=1525217&view=auto ============================================================================== --- tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSubclassTest.java (added) +++ tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSubclassTest.java Sat Sep 21 10:32:17 2013 @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.dyni; + +import org.apache.openejb.util.Join; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Implementation of methods and constructors: + * + * - Ensure all abstract methods were implemented to delegate to the InvocationHandler method + * - Ensure all constructors were carried forward to subclass + * + * Preservation of annotations + * + * - Ensure all annotations from the immediate parent class were copied + * - Ensure all annotations from parent constructors and constructor params were copied + * - Ensure all annotations from abstract ancestor methods and method params were copied + * + * @version $Rev$ $Date$ + */ +public class DynamicSubclassTest extends Assert { + + private static Invocation invocation; + + @Test + public void test() throws Exception { + final URLClassLoader loader = new URLClassLoader(new URL[0]); + + final Class subclass = DynamicSubclass.createSubclass(Blue.class, loader); + + final Constructor constructor = subclass.getConstructor(long.class); + final Blue blue = (Blue) constructor.newInstance(1l); + + final Class<?> generatedClass = blue.getClass(); + assertNotEquals(Blue.class, generatedClass); + + // Were class, constructor and constructor parameter annotations copied? + { + // class annotations? + assertEquals("blue", generatedClass.getAnnotation(Circle.class).value()); + + // constructor annotations? + assertEquals("blue()", generatedClass.getConstructor().getAnnotation(Oval.class).value()); + assertEquals("blue(long)", generatedClass.getConstructor(long.class).getAnnotation(Oval.class).value()); + + // constructor parameter annotations? + final Annotation annotation = generatedClass.getConstructor(long.class).getParameterAnnotations()[0][0]; + assertEquals("1", ((Triangle) annotation).value()); + } + + { // blue method + blue.blue(1); + assertNotNull(invocation); + final Method method = Blue.class.getDeclaredMethod("blue", int.class); + assertEquals(invocation.getMethod(), method); + assertEquals("1", Join.join(",", invocation.args)); + assertEquals("blue", method.getAnnotation(Square.class).value()); + assertEquals("blue", ((Triangle) method.getParameterAnnotations()[0][0]).value()); + } + + { // green method + blue.green("hello"); + assertNotNull(invocation); + final Method method = Green.class.getDeclaredMethod("green", String.class); + assertEquals(invocation.getMethod(), method); + assertEquals("hello", Join.join(",", invocation.args)); + assertEquals("green", method.getAnnotation(Square.class).value()); + assertEquals("green", ((Triangle) method.getParameterAnnotations()[0][0]).value()); + } + + { // blue method + blue.red(URI.create("foo://bar")); + assertNotNull(invocation); + final Method method = Red.class.getDeclaredMethod("red", URI.class); + assertEquals(invocation.getMethod(), method); + assertEquals("foo://bar", Join.join(",", invocation.getArgs())); + assertEquals("red", method.getAnnotation(Square.class).value()); + assertEquals("red", ((Triangle) method.getParameterAnnotations()[0][0]).value()); + } + } + + public static class Invocation { + private final Object proxy; + private final Method method; + private final Object[] args; + + public Invocation(Object proxy, Method method, Object[] args) { + this.proxy = proxy; + this.method = method; + this.args = args; + } + + public Object getProxy() { + return proxy; + } + + public Method getMethod() { + return method; + } + + public Object[] getArgs() { + return args; + } + } + + public static abstract class Color implements InvocationHandler { + + public Color() { + } + + public Color(URI uri, long foo) { + } + + // TODO: check to ensure this method is implemented, issue validation failure if not + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + invocation = new Invocation(proxy, method, args); + return null; + } + } + + @Circle("red") + public static abstract class Red extends Color { + + @Square("red") + public abstract void red(@Triangle("red") URI uri); + } + + @Circle("green") + public static abstract class Green extends Red { + + @Square("green") + public abstract void green(@Triangle("green") String v); + } + + @Circle("blue") + public static abstract class Blue extends Green { + + @Oval("blue()") + public Blue() { + } + + @Oval("blue(long)") + public Blue(@Triangle("1") long l) { + } + + @Square("blue") + public abstract void blue(@Triangle("blue") int v); + } + + + @Target(value = ElementType.TYPE) + @Retention(value = RetentionPolicy.RUNTIME) + public static @interface Circle { + String value(); + } + + @Target(value = ElementType.CONSTRUCTOR) + @Retention(value = RetentionPolicy.RUNTIME) + public static @interface Oval { + String value(); + } + + @Target(value = ElementType.METHOD) + @Retention(value = RetentionPolicy.RUNTIME) + public static @interface Square { + String value(); + } + + @Target(value = ElementType.PARAMETER) + @Retention(value = RetentionPolicy.RUNTIME) + public static @interface Triangle { + String value(); + } + + +} Propchange: tomee/tomee/trunk/container/openejb-core/src/test/java/org/apache/openejb/dyni/DynamicSubclassTest.java ------------------------------------------------------------------------------ svn:eol-style = native Copied: tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/DynamicSubclassEjbDeploymentTest.java (from r1524933, tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EjbDeploymentTest.java) URL: http://svn.apache.org/viewvc/tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/DynamicSubclassEjbDeploymentTest.java?p2=tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/DynamicSubclassEjbDeploymentTest.java&p1=tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EjbDeploymentTest.java&r1=1524933&r2=1525217&rev=1525217&view=diff ============================================================================== --- tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EjbDeploymentTest.java (original) +++ tomee/tomee/trunk/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/DynamicSubclassEjbDeploymentTest.java Sat Sep 21 10:32:17 2013 @@ -33,14 +33,17 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Request; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; import java.util.Properties; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; -public class EjbDeploymentTest { +public class DynamicSubclassEjbDeploymentTest { + private static EJBContainer container; - private static RESTIsCool service; + private static RESTIsVeryCool service; @BeforeClass public static void start() throws Exception { @@ -48,7 +51,7 @@ public class EjbDeploymentTest { properties.setProperty(DeploymentFilterable.CLASSPATH_INCLUDE, ".*openejb-cxf-rs.*"); properties.setProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "true"); container = EJBContainer.createEJBContainer(properties); - service = (RESTIsCool) container.getContext().lookup("java:/global/openejb-cxf-rs/RESTIsCool"); + service = (RESTIsVeryCool) container.getContext().lookup("java:/global/openejb-cxf-rs/RESTIsVeryCool"); } @AfterClass @@ -87,23 +90,21 @@ public class EjbDeploymentTest { @Stateless @Path("/ejb") - public static class RESTIsCool { + public abstract static class RESTIsVeryCool implements InvocationHandler { + @EJB private SimpleEJB simpleEJB; + @javax.ws.rs.core.Context Request request; @Path("/normal") @GET - public String normal() { - return simpleEJB.ok(); - } + public abstract String normal(); @Path("/rest") @GET - public String rest() { - return simpleEJB.ok(); - } + public abstract String rest(); @Path("/param") @GET @@ -116,5 +117,10 @@ public class EjbDeploymentTest { public boolean field() { return "GET".equals(request.getMethod()); } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return simpleEJB.ok(); + } } }