From dd15b4aea80d0e474802d30cf8388cb10241e434 Mon Sep 17 00:00:00 2001 From: Dirk Fauth Date: Tue, 13 Feb 2024 17:43:20 +0100 Subject: [PATCH 01/12] Fixes #37 - Update Eclipse Parsson Dependency (#38) * Fixes #37 - Update Eclipse Parsson Dependency * Fixes #37 - Update Eclipse Parsson Dependency * Fixes #37 - Update Eclipse Parsson Dependency Updated .bndrun files --- .../src/main/resources/archetype-resources/launch.bndrun | 2 +- .../src/main/resources/library/bndrun.bnd | 2 +- .../test.bndrun | 4 ++-- org.eclipse.osgitech.rest.tck/test.bndrun | 4 ++-- pom.xml | 5 +++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/org.eclipse.osgitech.rest.archetype/src/main/resources/archetype-resources/launch.bndrun b/org.eclipse.osgitech.rest.archetype/src/main/resources/archetype-resources/launch.bndrun index b561167..03012df 100644 --- a/org.eclipse.osgitech.rest.archetype/src/main/resources/archetype-resources/launch.bndrun +++ b/org.eclipse.osgitech.rest.archetype/src/main/resources/archetype-resources/launch.bndrun @@ -36,7 +36,7 @@ jersey.deps: \ org.apache.felix.http.jetty;version='[5.0.0,5.1)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.2)',\ org.apache.felix.scr;version='[2.2.6,2.3)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.2)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.2)',\ com.sun.xml.bind.jaxb-osgi;version='[4,4.1)',\ com.sun.activation.jakarta.activation;version='[2,2.1)',\ org.glassfish.hk2.api;version='[3.0.4,3.1)',\ diff --git a/org.eclipse.osgitech.rest.bnd.project.library/src/main/resources/library/bndrun.bnd b/org.eclipse.osgitech.rest.bnd.project.library/src/main/resources/library/bndrun.bnd index f22dfe4..c078633 100644 --- a/org.eclipse.osgitech.rest.bnd.project.library/src/main/resources/library/bndrun.bnd +++ b/org.eclipse.osgitech.rest.bnd.project.library/src/main/resources/library/bndrun.bnd @@ -26,7 +26,7 @@ jersey.deps: \ org.apache.felix.http.jetty;version='[5.0.0,5.1)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.2)',\ org.apache.felix.scr;version='[2.2.6,2.3)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.2)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.2)',\ com.sun.xml.bind.jaxb-osgi;version='[4,4.1)',\ com.sun.activation.jakarta.activation;version='[2,2.1)',\ org.glassfish.hk2.api;version='[3.0.4,3.1)',\ diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun index ece5cf0..eea00e6 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun +++ b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun @@ -30,7 +30,7 @@ org.apache.felix.cm.json;version='[2.0.0,2.1)',\ org.apache.felix.configadmin;version='[1.9.24,1.10)',\ org.apache.felix.configurator;version='[1.0.18,1.1)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.2)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.2)',\ org.osgi.util.converter;version='[1.0.9,1.1)' -runblacklist: \ @@ -67,7 +67,7 @@ org.osgi.util.promise;version='[1.3.0,1.3.1)',\ slf4j.api;version='[1.7.36,1.7.37)',\ slf4j.simple;version='[1.7.36,1.7.37)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.1.3)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.1.6)',\ org.glassfish.hk2.api;version='[3.0.5,3.0.6)',\ org.glassfish.hk2.external.aopalliance-repackaged;version='[3.0.5,3.0.6)',\ org.glassfish.hk2.locator;version='[3.0.5,3.0.6)',\ diff --git a/org.eclipse.osgitech.rest.tck/test.bndrun b/org.eclipse.osgitech.rest.tck/test.bndrun index 19c7f13..38156f9 100644 --- a/org.eclipse.osgitech.rest.tck/test.bndrun +++ b/org.eclipse.osgitech.rest.tck/test.bndrun @@ -31,7 +31,7 @@ org.apache.felix.cm.json;version='[2.0.0,2.1)',\ org.apache.felix.configadmin;version='[1.9.24,1.10)',\ org.apache.felix.configurator;version='[1.0.18,1.1)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.2)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.2)',\ org.osgi.util.converter;version='[1.0.9,1.1)' @@ -60,7 +60,7 @@ org.apache.felix.cm.json;version='[2.0.0,2.0.1)',\ org.apache.felix.configurator;version='[1.0.18,1.0.19)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.1.3)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.1.6)',\ assertj-core;version='[3.24.2,3.24.3)',\ net.bytebuddy.byte-buddy;version='[1.12.21,1.12.22)',\ org.apache.felix.scr;version='[2.2.6,2.2.7)',\ diff --git a/pom.xml b/pom.xml index 7fefb84..f5c3e49 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ 3.0.5 9.5 2.15.2 + 1.1.5 @@ -189,12 +190,12 @@ org.eclipse.parsson jakarta.json - 1.1.2 + ${parsson.version} org.eclipse.parsson parsson-media - 1.1.2 + ${parsson.version} org.javassist From 1af09003cdec3f394a3d5b55398ea9b3a8dddeb7 Mon Sep 17 00:00:00 2001 From: Tim Ward Date: Mon, 26 Feb 2024 14:37:08 +0000 Subject: [PATCH 02/12] Fix issues with generics in extension services The whiteboard implementation must create dynamic classes for each extension service to allow multiple instances of the same class to be registered. The existing generics handling was limited, and could not correctly represent unbound type variables, wildcards, nested generics, or reification of variables declared in super types. This commit enhances the generics processing in an attempt to cover all cases. Fixes #39 Signed-off-by: Tim Ward --- .../rest/proxy/ExtensionProxyFactory.java | 162 +++++++++-- .../rest/proxy/ExtensionProxyTest.java | 272 +++++++++++++++++- 2 files changed, 406 insertions(+), 28 deletions(-) diff --git a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java index 561fa23..48fdcf6 100644 --- a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java +++ b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java @@ -17,12 +17,19 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; @@ -54,13 +61,11 @@ public class ExtensionProxyFactory { * @param simpleName */ public static byte[] generateClass(String className, Object delegate, List> contracts) { - Map typeInfo = Arrays.stream(delegate.getClass().getGenericInterfaces()) - .filter(ParameterizedType.class::isInstance) - .map(ParameterizedType.class::cast) - .collect(Collectors.toMap(i -> i.getRawType().getTypeName(), Function.identity())); + Class delegateClazz = delegate.getClass(); + Map typeInfo = getInterfacesAndGenericSuperclasses(delegateClazz, new ArrayList<>(), new HashSet<>(contracts)) + .stream().collect(Collectors.toMap(i -> i.getRawType().getTypeName(), Function.identity())); - - String sig = generateGenericClassSignature(typeInfo, contracts); + String sig = generateGenericClassSignature(delegateClazz, typeInfo, contracts); try { @@ -73,7 +78,7 @@ public static byte[] generateClass(String className, Object delegate, List getInterfacesAndGenericSuperclasses(Class toCheck, List fromChildren, Set> remainingContracts) { + if(toCheck == Object.class) { + return fromChildren; + } + + for (java.lang.reflect.Type type : toCheck.getGenericInterfaces()) { + if(type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + if(remainingContracts.remove(pt.getRawType())) { + fromChildren.add(pt); + } + } + } + + java.lang.reflect.Type genericSuperclass = toCheck.getGenericSuperclass(); + if(genericSuperclass instanceof ParameterizedType) { + fromChildren.add((ParameterizedType) genericSuperclass); + } + + return getInterfacesAndGenericSuperclasses(toCheck.getSuperclass(), fromChildren, remainingContracts); + } + + private static Predicate> isUsedInTypeInfo(Map typeInfo) { + return tv -> typeInfo.values().stream() + .flatMap(ExtensionProxyFactory::toTypeVariables) + .anyMatch(t -> tv.getName().equals(t.getName())); + } + /** * @param typeInfo * @return */ - private static String generateGenericClassSignature(Map typeInfo, List> contracts) { + private static String generateGenericClassSignature(Class delegateClazz, Map typeInfo, List> contracts) { if(typeInfo.isEmpty()) { return null; } - SignatureWriter sw = new SignatureWriter(); - // Handle parent - SignatureVisitor sv = sw.visitSuperclass(); + SignatureWriter writer = new SignatureWriter(); + + // Handle class + Arrays.stream(delegateClazz.getTypeParameters()) + .filter(isUsedInTypeInfo(typeInfo)) + .forEach(tv -> { + writer.visitFormalTypeParameter(tv.getName()); + SignatureVisitor cb = writer.visitClassBound(); + Arrays.stream(tv.getBounds()).forEach(b -> visitTypeParameter(b, cb, delegateClazz, typeInfo)); + cb.visitEnd(); + }); + + // Generated class extends Object + SignatureVisitor sv = writer.visitSuperclass(); sv.visitClassType(OBJECT_INTERNAL_NAME); sv.visitEnd(); // Handle interfaces for(Class contract : contracts) { if(typeInfo.containsKey(contract.getName())) { - SignatureVisitor iv = sw.visitInterface(); + Class directDeclarer = delegateClazz; + check: for(;;) { + for(Class iface : directDeclarer.getInterfaces()) { + if(iface == contract) { + break check; + } + } + directDeclarer = directDeclarer.getSuperclass(); + if(directDeclarer == Object.class) { + throw new IllegalArgumentException("The contract " + contract + " is not implemented in the hierarchy"); + } + } + SignatureVisitor iv = writer.visitInterface(); iv.visitClassType(Type.getInternalName(contract)); for(java.lang.reflect.Type t : typeInfo.get(contract.getName()).getActualTypeArguments()) { - visitTypeParameter(t, iv); + if(TypeVariable.class.isInstance(t)) { + visitTypeParameter(t, iv, directDeclarer, typeInfo); + } else { + SignatureVisitor tav = iv.visitTypeArgument(SignatureVisitor.INSTANCEOF); + visitTypeParameter(t, tav, directDeclarer, typeInfo); + tav.visitEnd(); + } } iv.visitEnd(); } } - sw.visitEnd(); - return sw.toString(); + writer.visitEnd(); + return writer.toString(); + } + + private static java.lang.reflect.Type getPossibleReifiedTypeFor(TypeVariable tv, Class directDeclarer, Map typeInfo) { + ParameterizedType pt = typeInfo.get(directDeclarer.getName()); + if(pt != null) { + TypeVariable[] decParams = directDeclarer.getTypeParameters(); + for (int i = 0; i < decParams.length; i++) { + TypeVariable decTv = decParams[i]; + if(decTv.getName().equals(tv.getName())) { + return pt.getActualTypeArguments()[i]; + } + } + } + return tv; + } + + private static Stream> toTypeVariables(java.lang.reflect.Type t) { + if(t instanceof Class) { + return Stream.empty(); + } else if (t instanceof TypeVariable) { + return Stream.of((TypeVariable)t); + } else if (t instanceof ParameterizedType) { + return Arrays.stream(((ParameterizedType) t).getActualTypeArguments()) + .flatMap(ExtensionProxyFactory::toTypeVariables); + } else { + throw new IllegalArgumentException("Unkown type " + t.getClass()); + } } - private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv) { - SignatureVisitor pv = sv.visitTypeArgument('='); + private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Class directDeclarer, Map typeInfo) { if(t instanceof Class) { Class clazz = (Class) t; if(clazz.isPrimitive()) { - pv.visitBaseType(Type.getDescriptor(clazz).charAt(0)); + sv.visitBaseType(Type.getDescriptor(clazz).charAt(0)); } else if (clazz.isArray()) { - SignatureVisitor av = pv.visitArrayType(); - visitTypeParameter(clazz.getComponentType(), av); + SignatureVisitor av = sv.visitArrayType(); + visitTypeParameter(clazz.getComponentType(), av, directDeclarer, typeInfo); av.visitEnd(); } else { - pv.visitClassType(Type.getInternalName(clazz)); + sv.visitClassType(Type.getInternalName(clazz)); } } else if (t instanceof ParameterizedType){ ParameterizedType pt = (ParameterizedType) t; - pv.visitClassType(Type.getInternalName((Class)pt.getRawType())); - Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> visitTypeParameter(ta, pv)); - } - pv.visitEnd(); + sv.visitClassType(Type.getInternalName((Class)pt.getRawType())); + Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> visitTypeParameter(ta, sv, directDeclarer, typeInfo)); + } else if (t instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) t; + t = getPossibleReifiedTypeFor((TypeVariable)t, directDeclarer, typeInfo); + if(t == tv) { + sv.visitTypeArgument(SignatureVisitor.INSTANCEOF).visitTypeVariable(tv.getName()); + } else { + SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); + visitTypeParameter(t, tav, directDeclarer, typeInfo); + tav.visitEnd(); + } + } else if (t instanceof WildcardType) { + WildcardType wt = (WildcardType) t; + SignatureVisitor tav; + java.lang.reflect.Type[] types; + if(wt.getLowerBounds().length > 0) { + tav = sv.visitTypeArgument(SignatureVisitor.SUPER); + types = wt.getLowerBounds(); + } else { + tav = sv.visitTypeArgument(SignatureVisitor.EXTENDS); + types = wt.getUpperBounds(); + } + Arrays.stream(types).forEach(ty -> visitTypeParameter(ty, tav, directDeclarer, typeInfo)); + tav.visitEnd(); + } else { + throw new IllegalArgumentException("Unhandled generic type " + t.getClass() + " " + t.toString()); + } } private static void visitAnnotationMembers(Annotation a, AnnotationVisitor av) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index 2f30cea..2386b62 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -29,22 +29,26 @@ import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.Path; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ContextResolver; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; -import org.eclipse.osgitech.rest.proxy.ExtensionProxyFactory; -import org.junit.jupiter.api.Test; - /** * * @author timothyjward @@ -122,6 +126,204 @@ public void testRawExceptionMapper() throws Exception { } + @SuppressWarnings("unchecked") + @Test + public void testParameterExceptionMapper() throws Exception { + + ExceptionMapper em = new ParameterExceptionMapper<>(); + + Class proxyClazz = pcl.define("test.ExceptionMapper", em, + singletonList(ExceptionMapper.class)); + + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(1, typeParameters.length); + assertTrue(TypeVariable.class.isInstance(typeParameters[0])); + assertEquals("T", typeParameters[0].toString()); + Type[] bounds = ((TypeVariable)typeParameters[0]).getBounds(); + assertEquals(1, bounds.length); + assertEquals(Throwable.class, bounds[0]); + + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ExceptionMapper.class, pt.getRawType()); + assertTrue(TypeVariable.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("T", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> em); + + assertEquals(451, ((ExceptionMapper)instance) + .toResponse(new RuntimeException()).getStatus()); + + } + + @SuppressWarnings("unchecked") + @Test + public void testChildReifiedExceptionMapper() throws Exception { + + ExceptionMapper em = new ChildReifiedExceptionMapper(); + + Class proxyClazz = pcl.define("test.ExceptionMapper", em, + singletonList(ExceptionMapper.class)); + + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(0, typeParameters.length); + + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ExceptionMapper.class, pt.getRawType()); + assertEquals(WebApplicationException.class, pt.getActualTypeArguments()[0]); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> em); + + assertEquals(404, ((ExceptionMapper)instance) + .toResponse(new NotFoundException()).getStatus()); + + } + + @SuppressWarnings("unchecked") + @Test + public void testParameterContext() throws Exception { + + ContextResolver cr = new ParameterContext<>(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(1, typeParameters.length); + assertTrue(TypeVariable.class.isInstance(typeParameters[0])); + assertEquals("T", typeParameters[0].toString()); + Type[] bounds = ((TypeVariable)typeParameters[0]).getBounds(); + assertEquals(1, bounds.length); + assertEquals(Object.class, bounds[0]); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(TypeVariable.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("T", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals("", ((ContextResolver)instance) + .getContext(String.class)); + + } + + @SuppressWarnings("unchecked") + @Test + public void testExtendsParameterContext() throws Exception { + + ContextResolver> cr = new ExtendsParameterContext<>(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(1, typeParameters.length); + assertTrue(TypeVariable.class.isInstance(typeParameters[0])); + assertEquals("R", typeParameters[0].toString()); + Type[] bounds = ((TypeVariable)typeParameters[0]).getBounds(); + assertEquals(1, bounds.length); + assertEquals(Number.class, bounds[0]); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals(Collections.emptyList(), ((ContextResolver>)instance) + .getContext(String.class)); + + } + + @SuppressWarnings("unchecked") + @Test + public void testChildExtendsParameterContext() throws Exception { + + ContextResolver> cr = new ChildExtendsParameterContext(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(0, typeParameters.length); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals(Collections.singletonList(42.0d), ((ContextResolver>)instance) + .getContext(String.class)); + + } + + @SuppressWarnings("unchecked") + @Test + public void testWildcardParameterContext() throws Exception { + + ContextResolver> cr = new WildcardParameterContext(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals(Collections.emptyList(), ((ContextResolver>)instance) + .getContext(String.class)); + + } + @SuppressWarnings("unchecked") @Test public void testAnnotatedExceptionMapper() throws Exception { @@ -229,6 +431,70 @@ public Response toResponse(Throwable exception) { } + public static class ParameterExceptionMapper implements ExceptionMapper { + + /* + * (non-Javadoc) + * @see jakarta.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) + */ + @Override + public Response toResponse(T exception) { + // Unavailable for legal reasons + return Response.status(451).build(); + } + + } + + public static class ChildReifiedExceptionMapper extends ParameterExceptionMapper { + + @Override + public Response toResponse(WebApplicationException exception) { + return Response.status(exception.getResponse().getStatus()).build(); + } + + } + + public static class ParameterContext implements ContextResolver { + + @SuppressWarnings("unchecked") + @Override + public T getContext(Class type) { + try { + return (T) type.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + } + + public static class ExtendsParameterContext implements ContextResolver> { + + @Override + public List getContext(Class type) { + return new ArrayList<>(); + } + + } + + public static class ChildExtendsParameterContext extends ExtendsParameterContext { + + @Override + public List getContext(Class type) { + return Collections.singletonList(42.0d); + } + + } + + public static class WildcardParameterContext implements ContextResolver> { + + @Override + public List getContext(Class type) { + return new ArrayList<>(); + } + + } + @Path("boo") public static class AnnotatedExceptionMapper implements ExceptionMapper { From 89d320daf8e1a530e5c780b106158d6c8c110180 Mon Sep 17 00:00:00 2001 From: Tim Ward Date: Tue, 27 Feb 2024 15:56:13 +0000 Subject: [PATCH 03/12] Further work on Generics support I identified a missing test case where a Type Variable changes name in the generics declaration of an intermediate super class, and also where the Type Variable use is nested inside another generic declaration. Once added these tests showed up some further weaknesses which I have attempted to fix by establishing a context mapping of contract interface to implementing type and type to super type. This constrains the search through the type information and ensures that we only match type information that is relevant in the current context. Signed-off-by: Tim Ward --- .../rest/proxy/ExtensionProxyFactory.java | 116 +++++++++++------- .../rest/proxy/ExtensionProxyTest.java | 107 ++++++++++++++++ 2 files changed, 181 insertions(+), 42 deletions(-) diff --git a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java index 48fdcf6..14256b9 100644 --- a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java +++ b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java @@ -19,16 +19,14 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; -import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.function.BiPredicate; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.objectweb.asm.AnnotationVisitor; @@ -62,10 +60,12 @@ public class ExtensionProxyFactory { */ public static byte[] generateClass(String className, Object delegate, List> contracts) { Class delegateClazz = delegate.getClass(); - Map typeInfo = getInterfacesAndGenericSuperclasses(delegateClazz, new ArrayList<>(), new HashSet<>(contracts)) - .stream().collect(Collectors.toMap(i -> i.getRawType().getTypeName(), Function.identity())); + Map typeInfo = new HashMap<>(); + Map contextMapping = new HashMap<>(); + + populateInterfacesAndGenericSuperclasses(delegateClazz, typeInfo, contextMapping, new HashSet<>(contracts)); - String sig = generateGenericClassSignature(delegateClazz, typeInfo, contracts); + String sig = generateGenericClassSignature(delegateClazz, typeInfo, contextMapping, contracts); try { @@ -141,39 +141,72 @@ public static byte[] generateClass(String className, Object delegate, List getInterfacesAndGenericSuperclasses(Class toCheck, List fromChildren, Set> remainingContracts) { + private static void populateInterfacesAndGenericSuperclasses(Class toCheck, + Map typeInfo, Map contextMapping, Set> remainingContracts) { if(toCheck == Object.class) { - return fromChildren; + return; } for (java.lang.reflect.Type type : toCheck.getGenericInterfaces()) { if(type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; if(remainingContracts.remove(pt.getRawType())) { - fromChildren.add(pt); + String name = ((Class)pt.getRawType()).getName(); + typeInfo.put(name, pt); + contextMapping.put(name, toCheck.getName()); } } } java.lang.reflect.Type genericSuperclass = toCheck.getGenericSuperclass(); if(genericSuperclass instanceof ParameterizedType) { - fromChildren.add((ParameterizedType) genericSuperclass); + String name = toCheck.getSuperclass().getName(); + typeInfo.put(name, (ParameterizedType) genericSuperclass); + contextMapping.put(toCheck.getName(), name); } - return getInterfacesAndGenericSuperclasses(toCheck.getSuperclass(), fromChildren, remainingContracts); + populateInterfacesAndGenericSuperclasses(toCheck.getSuperclass(), typeInfo, contextMapping, remainingContracts); + return; } - private static Predicate> isUsedInTypeInfo(Map typeInfo) { - return tv -> typeInfo.values().stream() + private static boolean isUsedInContracts(TypeVariable> tv, Map typeInfo, Map context, List> contracts) { + BiPredicate contractUses = (varName, contextClass) -> contracts.stream() + .filter(c -> contextClass.equals(context.get(c.getName()))) + .map(c -> typeInfo.get(c.getName())) .flatMap(ExtensionProxyFactory::toTypeVariables) - .anyMatch(t -> tv.getName().equals(t.getName())); + .anyMatch(t -> varName.equals(t.getName())); + + String decClassName = tv.getGenericDeclaration().getName(); + String variableName = tv.getName(); + + if(contractUses.test(variableName, decClassName)) { + return true; + } + // Check the super class next + ParameterizedType superType = typeInfo.get(context.get(decClassName)); + if(superType == null) { + return false; + } + // Are any of the generic types of the super class linked to our type variable + // *and* then used in the contracts? Remember to check recursively up the hierarchy + java.lang.reflect.Type[] superTypeArguments = superType.getActualTypeArguments(); + for (int i = 0; i < superTypeArguments.length; i++) { + if(ExtensionProxyFactory.toTypeVariables(superTypeArguments[i]) + .anyMatch(t -> variableName.equals(t.getName()))) { + Class raw = (Class) superType.getRawType(); + if(isUsedInContracts(raw.getTypeParameters()[i], typeInfo, context, contracts)) { + return true; + } + } + } + return false; } /** * @param typeInfo * @return */ - private static String generateGenericClassSignature(Class delegateClazz, Map typeInfo, List> contracts) { + private static String generateGenericClassSignature(Class delegateClazz, Map typeInfo, Map context, List> contracts) { if(typeInfo.isEmpty()) { return null; } @@ -182,11 +215,11 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< // Handle class Arrays.stream(delegateClazz.getTypeParameters()) - .filter(isUsedInTypeInfo(typeInfo)) + .filter(tv -> isUsedInContracts(tv, typeInfo, context, contracts)) .forEach(tv -> { writer.visitFormalTypeParameter(tv.getName()); SignatureVisitor cb = writer.visitClassBound(); - Arrays.stream(tv.getBounds()).forEach(b -> visitTypeParameter(b, cb, delegateClazz, typeInfo)); + Arrays.stream(tv.getBounds()).forEach(b -> visitTypeParameter(b, cb, typeInfo, context)); cb.visitEnd(); }); @@ -198,26 +231,14 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< // Handle interfaces for(Class contract : contracts) { if(typeInfo.containsKey(contract.getName())) { - Class directDeclarer = delegateClazz; - check: for(;;) { - for(Class iface : directDeclarer.getInterfaces()) { - if(iface == contract) { - break check; - } - } - directDeclarer = directDeclarer.getSuperclass(); - if(directDeclarer == Object.class) { - throw new IllegalArgumentException("The contract " + contract + " is not implemented in the hierarchy"); - } - } SignatureVisitor iv = writer.visitInterface(); iv.visitClassType(Type.getInternalName(contract)); for(java.lang.reflect.Type t : typeInfo.get(contract.getName()).getActualTypeArguments()) { if(TypeVariable.class.isInstance(t)) { - visitTypeParameter(t, iv, directDeclarer, typeInfo); + visitTypeParameter(t, iv, typeInfo, context); } else { SignatureVisitor tav = iv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(t, tav, directDeclarer, typeInfo); + visitTypeParameter(t, tav, typeInfo, context); tav.visitEnd(); } } @@ -229,14 +250,21 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< return writer.toString(); } - private static java.lang.reflect.Type getPossibleReifiedTypeFor(TypeVariable tv, Class directDeclarer, Map typeInfo) { - ParameterizedType pt = typeInfo.get(directDeclarer.getName()); + private static java.lang.reflect.Type getPossibleReifiedTypeFor(TypeVariable tv, Map typeInfo, Map context) { + Class declaringType = (Class) tv.getGenericDeclaration(); + + ParameterizedType pt = typeInfo.get(declaringType.getName()); if(pt != null) { - TypeVariable[] decParams = directDeclarer.getTypeParameters(); + TypeVariable[] decParams = declaringType.getTypeParameters(); for (int i = 0; i < decParams.length; i++) { TypeVariable decTv = decParams[i]; if(decTv.getName().equals(tv.getName())) { - return pt.getActualTypeArguments()[i]; + java.lang.reflect.Type type = pt.getActualTypeArguments()[i]; + if(type instanceof TypeVariable) { + return getPossibleReifiedTypeFor((TypeVariable) type, typeInfo, context); + } else { + return type; + } } } } @@ -256,14 +284,14 @@ private static Stream> toTypeVariables(java.lang.reflect.Type t) } } - private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Class directDeclarer, Map typeInfo) { + private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Map typeInfo, Map context) { if(t instanceof Class) { Class clazz = (Class) t; if(clazz.isPrimitive()) { sv.visitBaseType(Type.getDescriptor(clazz).charAt(0)); } else if (clazz.isArray()) { SignatureVisitor av = sv.visitArrayType(); - visitTypeParameter(clazz.getComponentType(), av, directDeclarer, typeInfo); + visitTypeParameter(clazz.getComponentType(), av, typeInfo, context); av.visitEnd(); } else { sv.visitClassType(Type.getInternalName(clazz)); @@ -271,15 +299,19 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito } else if (t instanceof ParameterizedType){ ParameterizedType pt = (ParameterizedType) t; sv.visitClassType(Type.getInternalName((Class)pt.getRawType())); - Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> visitTypeParameter(ta, sv, directDeclarer, typeInfo)); + Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> { + SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); + visitTypeParameter(ta, tav, typeInfo, context); + tav.visitEnd(); + }); } else if (t instanceof TypeVariable) { TypeVariable tv = (TypeVariable) t; - t = getPossibleReifiedTypeFor((TypeVariable)t, directDeclarer, typeInfo); + t = getPossibleReifiedTypeFor((TypeVariable)t, typeInfo, context); if(t == tv) { sv.visitTypeArgument(SignatureVisitor.INSTANCEOF).visitTypeVariable(tv.getName()); } else { SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(t, tav, directDeclarer, typeInfo); + visitTypeParameter(t, tav, typeInfo, context); tav.visitEnd(); } } else if (t instanceof WildcardType) { @@ -293,7 +325,7 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito tav = sv.visitTypeArgument(SignatureVisitor.EXTENDS); types = wt.getUpperBounds(); } - Arrays.stream(types).forEach(ty -> visitTypeParameter(ty, tav, directDeclarer, typeInfo)); + Arrays.stream(types).forEach(ty -> visitTypeParameter(ty, tav, typeInfo, context)); tav.visitEnd(); } else { throw new IllegalArgumentException("Unhandled generic type " + t.getClass() + " " + t.toString()); diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index 2386b62..bf72aff 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -323,6 +323,91 @@ public void testWildcardParameterContext() throws Exception { .getContext(String.class)); } + + @Test + public void testRedirectsParameterContext() throws Exception { + + ContextResolver cr = new RedirectsParameterContext<>(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(1, typeParameters.length); + assertTrue(TypeVariable.class.isInstance(typeParameters[0])); + assertEquals("R", typeParameters[0].toString()); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(TypeVariable.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("R", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals("", ((ContextResolver)instance) + .getContext(String.class)); + + } + + @Test + public void testChildExtendsRedirectedParameterContext() throws Exception { + + ContextResolver cr = new ChildExtendsRedirectedParameterContext(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(0, typeParameters.length); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertEquals(Double.class, pt.getActualTypeArguments()[0]); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals(42.0d, ((ContextResolver)instance) + .getContext(String.class)); + + } + @Test + public void testChildExtraExtendsRedirectedParameterContext() throws Exception { + + ContextResolver> cr = new ChildExtraExtendsRedirectedParameterContext(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("java.util.List", pt.getActualTypeArguments()[0].getTypeName()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals(Collections.singletonList(17.0d), ((ContextResolver)instance) + .getContext(String.class)); + + } @SuppressWarnings("unchecked") @Test @@ -494,6 +579,28 @@ public List getContext(Class type) { } } + + public static class RedirectsParameterContext extends ParameterContext { + + } + + public static class ChildExtendsRedirectedParameterContext extends RedirectsParameterContext { + + @Override + public Double getContext(Class type) { + return 42.0d; + } + + } + + public static class ChildExtraExtendsRedirectedParameterContext extends RedirectsParameterContext> { + + @Override + public List getContext(Class type) { + return Collections.singletonList(17.0d); + } + + } @Path("boo") public static class AnnotatedExceptionMapper implements ExceptionMapper { From e5187238558feeb0ba54d26b90b4297d2b28a326 Mon Sep 17 00:00:00 2001 From: Tim Ward Date: Tue, 27 Feb 2024 16:36:21 +0000 Subject: [PATCH 04/12] Additional tests for Generics processing Signed-off-by: Tim Ward --- .../rest/proxy/ExtensionProxyTest.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index bf72aff..ae57cad 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -409,6 +410,68 @@ public void testChildExtraExtendsRedirectedParameterContext() throws Exception { } + @Test + public void testIndirectlyRedirectsParameterContext() throws Exception { + + ContextResolver cr = new IndirectlyRedirectsParameterContext<>(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(1, typeParameters.length); + assertTrue(TypeVariable.class.isInstance(typeParameters[0])); + assertEquals("R", typeParameters[0].toString()); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals("", ((ContextResolver)instance) + .getContext(String.class)); + + } + + @Test + public void testChildExtraExtendsIndirectlyRedirectsParameterContext() throws Exception { + + ContextResolver cr = new ChildExtraExtendsIndirectlyRedirectedParameterContext(); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, + singletonList(ContextResolver.class)); + + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); + assertEquals(0, typeParameters.length); + + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(1, genericInterfaces.length); + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(ContextResolver.class, pt.getRawType()); + assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); + assertEquals("java.util.List>", pt.getActualTypeArguments()[0].toString()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> cr); + + assertEquals(Collections.singletonList(Collections.singletonMap("foo", 42)), ((ContextResolver)instance) + .getContext(String.class)); + + } + @SuppressWarnings("unchecked") @Test public void testAnnotatedExceptionMapper() throws Exception { @@ -601,6 +664,19 @@ public List getContext(Class type) { } } + + public static class IndirectlyRedirectsParameterContext extends ParameterContext> { + + } + + public static class ChildExtraExtendsIndirectlyRedirectedParameterContext extends IndirectlyRedirectsParameterContext> { + + @Override + public List> getContext(Class type) { + return Collections.singletonList(Collections.singletonMap("foo", 42)); + } + + } @Path("boo") public static class AnnotatedExceptionMapper implements ExceptionMapper { From cc6ac2bb60f3edaeae9d5f580e860b6bb54f6dc1 Mon Sep 17 00:00:00 2001 From: Mark Hoffmann Date: Wed, 20 Mar 2024 10:32:21 +0100 Subject: [PATCH 05/12] fixes #43 - removed unused dependency Thanks for reporting! Signed-off-by: Mark Hoffmann --- org.eclipse.osgitech.rest/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/org.eclipse.osgitech.rest/pom.xml b/org.eclipse.osgitech.rest/pom.xml index ad0e7bf..4e64b3f 100644 --- a/org.eclipse.osgitech.rest/pom.xml +++ b/org.eclipse.osgitech.rest/pom.xml @@ -94,10 +94,6 @@ org.apache.felix org.apache.felix.http.servlet-api - - org.apache.felix - org.apache.felix.http.jetty - org.glassfish.jersey.containers jersey-container-servlet From 3ac76b6f7fbadaba069e4f8d58531a71a05fe4ef Mon Sep 17 00:00:00 2001 From: Mark Hoffmann Date: Wed, 20 Mar 2024 10:34:06 +0100 Subject: [PATCH 06/12] Cleanup warning - removed unnused imports - removed other warnings Signed-off-by: Mark Hoffmann --- org.eclipse.osgitech.rest.bnd.library/bnd.bnd | 2 -- .../resources}/library/workspace/jakartarest.mvn | 0 .../main/resources}/library/workspace/workspace.bnd | 0 .../whiteboard/tests/ServletWhiteboardTest.java | 4 +++- .../test.bndrun | 10 +++++----- .../pom.xml | 6 ++++++ .../JakartarsServletWhiteboardRuntimeComponent.java | 2 -- .../ServletWhiteboardBasedJerseyServiceRuntime.java | 2 -- .../osgitech/rest/tests/ResourceDTOTests.java | 1 - .../rest/JerseyApplicationProviderTest.java | 13 ++----------- .../rest/util/OptionalResponseFilterTest.java | 1 - 11 files changed, 16 insertions(+), 25 deletions(-) rename org.eclipse.osgitech.rest.bnd.library/{ => src/main/resources}/library/workspace/jakartarest.mvn (100%) rename org.eclipse.osgitech.rest.bnd.library/{ => src/main/resources}/library/workspace/workspace.bnd (100%) diff --git a/org.eclipse.osgitech.rest.bnd.library/bnd.bnd b/org.eclipse.osgitech.rest.bnd.library/bnd.bnd index cd29da4..f14c706 100644 --- a/org.eclipse.osgitech.rest.bnd.library/bnd.bnd +++ b/org.eclipse.osgitech.rest.bnd.library/bnd.bnd @@ -7,6 +7,4 @@ Provide-Capability: \ bnd.library;\ bnd.library = jakartaREST; \ path = library/workspace - --includeresource: {library=library} diff --git a/org.eclipse.osgitech.rest.bnd.library/library/workspace/jakartarest.mvn b/org.eclipse.osgitech.rest.bnd.library/src/main/resources/library/workspace/jakartarest.mvn similarity index 100% rename from org.eclipse.osgitech.rest.bnd.library/library/workspace/jakartarest.mvn rename to org.eclipse.osgitech.rest.bnd.library/src/main/resources/library/workspace/jakartarest.mvn diff --git a/org.eclipse.osgitech.rest.bnd.library/library/workspace/workspace.bnd b/org.eclipse.osgitech.rest.bnd.library/src/main/resources/library/workspace/workspace.bnd similarity index 100% rename from org.eclipse.osgitech.rest.bnd.library/library/workspace/workspace.bnd rename to org.eclipse.osgitech.rest.bnd.library/src/main/resources/library/workspace/workspace.bnd diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java index bdc37e2..e35787a 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java +++ b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java @@ -44,7 +44,6 @@ import org.osgi.service.cm.Configuration; import org.osgi.service.jakartars.runtime.JakartarsServiceRuntime; import org.osgi.service.jakartars.whiteboard.JakartarsWhiteboardConstants; -import org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants; import org.osgi.test.common.annotation.InjectBundleContext; import org.osgi.test.common.annotation.config.InjectConfiguration; import org.osgi.test.common.annotation.config.WithFactoryConfiguration; @@ -222,6 +221,9 @@ public void testServletWhiteboardDefaultContext(@InjectBundleContext BundleConte properties.put(HTTP_WHITEBOARD_SERVLET_PATTERN, "/servlet"); properties.put(HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=" + HTTP_WHITEBOARD_DEFAULT_CONTEXT_NAME + ")"); ctx.registerService(Servlet.class, new HttpServlet() { + /** serialVersionUID */ + private static final long serialVersionUID = 1L; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.getWriter().print("Hello Servlet"); diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun index eea00e6..a52fce3 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun +++ b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/test.bndrun @@ -53,10 +53,6 @@ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ org.apache.felix.scr;version='[2.2.6,2.2.7)',\ org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\ - org.eclipse.osgitech.rest;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.servlet.whiteboard;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.servlet.whiteboard.tests-tests;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.sse;version='[1.0.0,1.0.1)',\ org.osgi.service.component;version='[1.5.1,1.5.2)',\ org.osgi.service.jakartars;version='[2.0.0,2.0.1)',\ org.osgi.test.common;version='[1.2.1,1.2.2)',\ @@ -84,4 +80,8 @@ junit-jupiter-api;version='[5.9.2,5.9.3)',\ junit-jupiter-params;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ - org.opentest4j;version='[1.2.0,1.2.1)' \ No newline at end of file + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.eclipse.osgitech.rest;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.servlet.whiteboard;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.servlet.whiteboard.tests-tests;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.sse;version='[1.2.3,1.2.4)' \ No newline at end of file diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard/pom.xml b/org.eclipse.osgitech.rest.servlet.whiteboard/pom.xml index 0260de4..000afc0 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard/pom.xml +++ b/org.eclipse.osgitech.rest.servlet.whiteboard/pom.xml @@ -88,6 +88,11 @@ org.apache.aries.spifly.dynamic.framework.extension org.apache.aries.spifly.dynamic.framework.extension.jar + + org.slf4j + slf4j-api + slf4j-api.jar + org.slf4j slf4j-simple @@ -131,6 +136,7 @@ ${project.build.directory}/bundles/org.apache.felix.scr.jar ${project.build.directory}/bundles/org.apache.felix.http.jetty.jar ${project.build.directory}/bundles/org.apache.aries.spifly.dynamic.framework.extension.jar + ${project.build.directory}/bundles/slf4j-api.jar ${project.build.directory}/bundles/slf4j-simple.jar diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java index 3fda758..6ebcb23 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java +++ b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java @@ -16,7 +16,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; import org.eclipse.osgitech.rest.provider.JerseyConstants; import org.osgi.framework.BundleContext; @@ -50,7 +49,6 @@ target = JerseyConstants.JERSEY_RUNTIME_CONDITION)) public class JakartarsServletWhiteboardRuntimeComponent { - private static Logger logger = Logger.getLogger(JakartarsServletWhiteboardRuntimeComponent.class.getName()); private BundleContext context; private String target; private String basePath; diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java index c259290..627c4ec 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java +++ b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java @@ -34,7 +34,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Logger; import org.eclipse.osgitech.rest.annotations.ProvideRuntimeAdapter; import org.eclipse.osgitech.rest.helper.JerseyHelper; @@ -61,7 +60,6 @@ @RequireHttpWhiteboard public class ServletWhiteboardBasedJerseyServiceRuntime { - private final Logger logger = Logger.getLogger(ServletWhiteboardBasedJerseyServiceRuntime.class.getName()); private final BundleContext context; private final String basePath; private final ServiceReference runtimeTarget; diff --git a/org.eclipse.osgitech.rest.tests/src/test/java/org/eclipse/osgitech/rest/tests/ResourceDTOTests.java b/org.eclipse.osgitech.rest.tests/src/test/java/org/eclipse/osgitech/rest/tests/ResourceDTOTests.java index fb5890c..a3e61ad 100644 --- a/org.eclipse.osgitech.rest.tests/src/test/java/org/eclipse/osgitech/rest/tests/ResourceDTOTests.java +++ b/org.eclipse.osgitech.rest.tests/src/test/java/org/eclipse/osgitech/rest/tests/ResourceDTOTests.java @@ -13,7 +13,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Hashtable; diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/JerseyApplicationProviderTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/JerseyApplicationProviderTest.java index 79b1454..9029721 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/JerseyApplicationProviderTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/JerseyApplicationProviderTest.java @@ -16,34 +16,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; import java.util.HashMap; import java.util.Map; -import jakarta.ws.rs.core.Application; -import jakarta.ws.rs.ext.MessageBodyReader; - -import org.eclipse.osgitech.rest.resources.TestApplication; -import org.eclipse.osgitech.rest.resources.TestExtension; import org.eclipse.osgitech.rest.resources.TestLegacyApplication; -import org.eclipse.osgitech.rest.resources.TestResource; import org.eclipse.osgitech.rest.runtime.application.JerseyApplicationProvider; -import org.eclipse.osgitech.rest.runtime.application.JerseyExtensionProvider; -import org.eclipse.osgitech.rest.runtime.application.JerseyResourceProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.osgi.framework.Constants; import org.osgi.framework.ServiceObjects; import org.osgi.service.jakartars.runtime.dto.BaseApplicationDTO; import org.osgi.service.jakartars.runtime.dto.DTOConstants; import org.osgi.service.jakartars.runtime.dto.FailedApplicationDTO; import org.osgi.service.jakartars.whiteboard.JakartarsWhiteboardConstants; +import jakarta.ws.rs.core.Application; + /** * * @author Mark Hoffmann diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/util/OptionalResponseFilterTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/util/OptionalResponseFilterTest.java index d8068ec..7d5ce13 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/util/OptionalResponseFilterTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/util/OptionalResponseFilterTest.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.util.Optional; -import org.eclipse.osgitech.rest.util.OptionalResponseFilter; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; From b06537922f8da9b3012269cd257e4f494e1371de Mon Sep 17 00:00:00 2001 From: Dirk Fauth Date: Wed, 27 Mar 2024 14:54:30 +0100 Subject: [PATCH 07/12] Update README.MD (#40) --- README.MD | 74 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/README.MD b/README.MD index 8ede9e9..e4703e0 100644 --- a/README.MD +++ b/README.MD @@ -77,12 +77,12 @@ You can change various server setting by using the OSGi Configurator or the Conf { ":configurator:resource-version": 1, - "JakartarsWhiteboardComponent": - { - "jersey.port": 8081, - "jersey.jakartars.whiteboard.name" : "demo", - "jersey.context.path" : "demo" - } + "JakartarsWhiteboardComponent": + { + "jersey.port": 8081, + "jersey.jakartars.whiteboard.name" : "demo", + "jersey.context.path" : "demo" + } } ``` @@ -90,6 +90,23 @@ This would run the server at http://localhost:8081/demo +The following properties are supported for configuring the Whiteboard on Jersey: + +| Parameter | Description | Default | +| --- | --- | -- | +|`jersey.schema`| The schema under which the services should be available. | http | +|`jersey.host`| The host under which the services should be available. | localhost | +|`jersey.port`| The port under which the services should be available. | 8181 | +|`jersey.context.path`| The base context path of the whiteboard. | /rest | +|`jersey.jakartars.whiteboard.name`| The name of the whiteboard| Jersey REST | +|`jersey.disable.sessions`| Enable/disable session handling in Jetty.
Disabled by default as REST services are stateless. | `true` | + +The definition of these properties is located in [JerseyConstants](https://github.com/osgi/jakartarest-osgi/blob/main/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/provider/JerseyConstants.java). + +**_Note:_** +The default value for `jersey.context.path` is `/rest`. So if you don't configure a value via the _configurator.json_ file, your services will be available via the `rest` context path. This is also the case for a custom Jakarta-RS application. If you don't want to use a context path, you explicitly have to set it to an empty value. + + Please note, that the Felix Jetty implementation runs the OSGi HTTP Service by default at port 8080. **It may come to an conflict, with the port in your configuration.** @@ -109,20 +126,20 @@ You can change various setting by using the OSGi Configurator or the Configurati ```json { - "org.apache.felix.http~demo": - { - "org.osgi.service.http.port": 8081, - "org.osgi.service.http.host": "localhost", - "org.apache.felix.http.context_path": "demo", - "org.apache.felix.http.name": "Demo HTTP Whiteboard", - "org.apache.felix.http.runtime.init.id": "demowb" - }, - "JakartarsServletWhiteboardRuntimeComponent~demo": - { - "jersey.jakartars.whiteboard.name" : "Demo Jakarta REST Whiteboard", - "jersey.context.path" : "rest", - "osgi.http.whiteboard.target" : "(id=demowb)" - } + "org.apache.felix.http~demo": + { + "org.osgi.service.http.port": 8081, + "org.osgi.service.http.host": "localhost", + "org.apache.felix.http.context_path": "demo", + "org.apache.felix.http.name": "Demo HTTP Whiteboard", + "org.apache.felix.http.runtime.init.id": "demowb" + }, + "JakartarsServletWhiteboardRuntimeComponent~demo": + { + "jersey.jakartars.whiteboard.name" : "Demo Jakarta REST Whiteboard", + "jersey.context.path" : "rest", + "osgi.http.whiteboard.target" : "(id=demowb)" + } } ``` @@ -130,6 +147,18 @@ This would run the Jakarta REST Whiteboard implementation at: http://localhost:8081/demo/rest +The first block `org.apache.felix.http~demo` is used to configure the _Apache Felix HTTP Service_ service factory. Details about the configuration options are available in the [Apache Felix HTTP Service Wiki](https://cwiki.apache.org/confluence/display/FELIX/Apache+Felix+HTTP+Service). + +The second block `JakartarsServletWhiteboardRuntimeComponent~demo` is used to configure the whiteboard service factory with the Servlet Whiteboard. The following properties are supported for configuring the Whiteboard on Servlet Whiteboard: + +| Parameter | Description | Default | +| --- | --- | -- | +|`jersey.context.path`| The base context path of the whiteboard. | / | +|`jersey.jakartars.whiteboard.name`| The name of the whiteboard| Jersey REST | +|`osgi.http.whiteboard.target`| Service property specifying the target filter to select the Http Whiteboard implementation to process the service.
The value is an LDAP style filter that points to the id defined in `org.apache.felix.http.runtime.init.id`. | - | + +The definition of these properties is located in [JerseyConstants](https://github.com/osgi/jakartarest-osgi/blob/main/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/provider/JerseyConstants.java). + Please note, that the Felix Jetty implementation runs the OSGi HTTP Service by default at port 8080. **It may come to an conflict, with the port in your configuration.** @@ -141,7 +170,6 @@ Therefore you may set the system property `org.osgi.service.http.port=-1` to dea When using the Jakarta REST Whiteboard, you just have to register your REST resources and extensions as a service. There are some useful Meta-Annotations, that create component properties for you. ```java -@RequireJakartarsWhiteboard @JakartarsResource @JakartarsName("demo") @Component(service = DemoResource.class, scope = ServiceScope.PROTOTYPE) @@ -157,10 +185,6 @@ public class DemoResource { } ``` -With the `@RequireJakartarsWhiteboard` annotation, you define the requirement to the Jakarta REST Whiteboard implementation. In our case it is Jersey. You then need the Whiteboard dependencies in you workspace. - -Please note, that you only need to define `@RequireJakartarsWhiteboard` once in your bundle! - ## Maven Example Archetype The module *org.eclipse.osgitech.rest.archetype* contains a Maven template to create a sample project. From f3d490222e4df4b139e5d952bc2ab43e63a124fb Mon Sep 17 00:00:00 2001 From: Juergen Albert Date: Thu, 19 Sep 2024 13:09:51 +0200 Subject: [PATCH 08/12] whiteboard servlet runtime now forwards all properties fixes #46 Signed-off-by: Juergen Albert --- .../tests/ServletWhiteboardTest.java | 8 ++++++++ .../OSGI-INF/configurator/config.json | 3 ++- ...tarsServletWhiteboardRuntimeComponent.java | 4 +++- ...etWhiteboardBasedJerseyServiceRuntime.java | 9 +++++---- org.eclipse.osgitech.rest.tck/test.bndrun | 15 ++++++++++----- org.eclipse.osgitech.rest.tck/test2.bndrun | 19 ++++++++++++------- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java index e35787a..40d06ca 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java +++ b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/java/org/eclipse/osgitech/rest/servlet/whiteboard/tests/ServletWhiteboardTest.java @@ -263,5 +263,13 @@ protected String getBaseURI(ServiceReference runtime) { throw new IllegalArgumentException( "The JAXRS Service Runtime did not declare an endpoint property"); } + + @Test + public void testWhiteboardPropertiesForward() throws Exception { + ServiceReference serviceRuntime = tracker.getServiceReference(); + Object object = serviceRuntime.getProperties().get("addition.property"); + assertEquals("test.property", object); + + } } diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/resources/OSGI-INF/configurator/config.json b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/resources/OSGI-INF/configurator/config.json index 3664c8d..35b4a63 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/resources/OSGI-INF/configurator/config.json +++ b/org.eclipse.osgitech.rest.servlet.whiteboard.tests/src/test/resources/OSGI-INF/configurator/config.json @@ -13,6 +13,7 @@ { "jersey.jakartars.whiteboard.name" : "JRSWB", "jersey.context.path" : "test", - "osgi.http.whiteboard.target" : "(id=SWB)" + "osgi.http.whiteboard.target" : "(id=SWB)", + "addition.property" : "test.property" } } diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java index 6ebcb23..985e84b 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java +++ b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/JakartarsServletWhiteboardRuntimeComponent.java @@ -53,6 +53,7 @@ public class JakartarsServletWhiteboardRuntimeComponent { private String target; private String basePath; private ServiceTracker httpRuntimeTracker; + private Map props; /** * Called on component activation @@ -63,6 +64,7 @@ public class JakartarsServletWhiteboardRuntimeComponent { public void activate(BundleContext context, Map props) throws ConfigurationException { this.context = context; + this.props = props; target = (String) props.get(HttpWhiteboardConstants.HTTP_WHITEBOARD_TARGET); basePath = (String) props.getOrDefault(JerseyConstants.JERSEY_CONTEXT_PATH, "/"); openTracker(); @@ -121,7 +123,7 @@ private HttpServiceTracker(BundleContext context, Filter filter, @Override public ServletWhiteboardBasedJerseyServiceRuntime addingService(ServiceReference reference) { - return new ServletWhiteboardBasedJerseyServiceRuntime(context, basePath, reference); + return new ServletWhiteboardBasedJerseyServiceRuntime(context, basePath, reference, props); } @Override diff --git a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java index 627c4ec..060e8eb 100644 --- a/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java +++ b/org.eclipse.osgitech.rest.servlet.whiteboard/src/main/java/org/eclipse/osgitech/rest/runtime/httpwhiteboard/ServletWhiteboardBasedJerseyServiceRuntime.java @@ -83,16 +83,17 @@ public RestContext(ServiceRegistration contextHelperReg, } public ServletWhiteboardBasedJerseyServiceRuntime(BundleContext context, String basePath, - ServiceReference runtimeTarget) { + ServiceReference runtimeTarget, Map props) { this.context = context; this.basePath = basePath; this.runtimeTarget = runtimeTarget; httpId = (Long) runtimeTarget.getProperty(SERVICE_ID); this.httpWhiteboardTarget = String.format("(%s=%s)", SERVICE_ID, httpId); this.runtime = new JerseyServiceRuntime<>(context, this::registerContainer, this::unregisterContainer); - - runtime.start(Map.of(JAKARTA_RS_SERVICE_ENDPOINT, getURLs(), - SERVICE_DESCRIPTION, "REST whiteboard for HttpServiceRuntime " + httpId)); + Map runtimeProperties = new HashMap(props); + runtimeProperties.put(JAKARTA_RS_SERVICE_ENDPOINT, getURLs()); + runtimeProperties.put(SERVICE_DESCRIPTION, "REST whiteboard for HttpServiceRuntime " + httpId); + runtime.start(runtimeProperties); } diff --git a/org.eclipse.osgitech.rest.tck/test.bndrun b/org.eclipse.osgitech.rest.tck/test.bndrun index 38156f9..93c6fc3 100644 --- a/org.eclipse.osgitech.rest.tck/test.bndrun +++ b/org.eclipse.osgitech.rest.tck/test.bndrun @@ -65,10 +65,6 @@ net.bytebuddy.byte-buddy;version='[1.12.21,1.12.22)',\ org.apache.felix.scr;version='[2.2.6,2.2.7)',\ org.osgi.service.component;version='[1.5.1,1.5.2)',\ - org.eclipse.osgitech.rest;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.jetty;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.sse;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.tck-tests;version='[1.0.0,1.0.1)',\ org.glassfish.hk2.api;version='[3.0.5,3.0.6)',\ org.glassfish.hk2.external.aopalliance-repackaged;version='[3.0.5,3.0.6)',\ org.glassfish.hk2.locator;version='[3.0.5,3.0.6)',\ @@ -85,4 +81,13 @@ junit-jupiter-api;version='[5.9.2,5.9.3)',\ junit-jupiter-params;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ - org.opentest4j;version='[1.2.0,1.2.1)' \ No newline at end of file + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.eclipse.jetty.http;version='[11.0.13,11.0.14)',\ + org.eclipse.jetty.io;version='[11.0.13,11.0.14)',\ + org.eclipse.jetty.security;version='[11.0.13,11.0.14)',\ + org.eclipse.jetty.server;version='[11.0.13,11.0.14)',\ + org.eclipse.jetty.util;version='[11.0.13,11.0.14)',\ + org.eclipse.osgitech.rest;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.jetty;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.sse;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.tck-tests;version='[1.2.3,1.2.4)' \ No newline at end of file diff --git a/org.eclipse.osgitech.rest.tck/test2.bndrun b/org.eclipse.osgitech.rest.tck/test2.bndrun index fc0f4d6..ec90b02 100644 --- a/org.eclipse.osgitech.rest.tck/test2.bndrun +++ b/org.eclipse.osgitech.rest.tck/test2.bndrun @@ -9,7 +9,9 @@ osgi.extender;filter:='(osgi.extender=osgi.configurator)',\ bnd.identity;id='org.eclipse.osgitech.rest.tck-tests',\ bnd.identity;id='org.osgi.test.cases.jakartars',\ - bnd.identity;id='org.eclipse.osgitech.rest.servlet.whiteboard' + bnd.identity;id='org.eclipse.osgitech.rest.servlet.whiteboard',\ + bnd.identity;id='org.apache.felix.gogo.command',\ + bnd.identity;id='org.apache.felix.gogo.runtime' -runblacklist: \ bnd.identity;id='org.osgi.service.cm',\ @@ -60,15 +62,10 @@ org.apache.felix.cm.json;version='[2.0.0,2.0.1)',\ org.apache.felix.configurator;version='[1.0.18,1.0.19)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ - org.eclipse.parsson.jakarta.json;version='[1.1.2,1.1.3)',\ assertj-core;version='[3.24.2,3.24.3)',\ net.bytebuddy.byte-buddy;version='[1.12.21,1.12.22)',\ org.apache.felix.scr;version='[2.2.6,2.2.7)',\ org.osgi.service.component;version='[1.5.1,1.5.2)',\ - org.eclipse.osgitech.rest;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.servlet.whiteboard;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.sse;version='[1.0.0,1.0.1)',\ - org.eclipse.osgitech.rest.tck-tests;version='[1.0.0,1.0.1)',\ org.glassfish.hk2.api;version='[3.0.5,3.0.6)',\ org.glassfish.hk2.external.aopalliance-repackaged;version='[3.0.5,3.0.6)',\ org.glassfish.hk2.locator;version='[3.0.5,3.0.6)',\ @@ -85,4 +82,12 @@ junit-jupiter-api;version='[5.9.2,5.9.3)',\ junit-jupiter-params;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ - org.opentest4j;version='[1.2.0,1.2.1)' \ No newline at end of file + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.eclipse.osgitech.rest;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.servlet.whiteboard;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.sse;version='[1.2.3,1.2.4)',\ + org.eclipse.osgitech.rest.tck-tests;version='[1.2.3,1.2.4)',\ + org.eclipse.parsson.jakarta.json;version='[1.1.5,1.1.6)',\ + org.osgi.service.servlet;version='[2.0.0,2.0.1)',\ + org.apache.felix.gogo.command;version='[1.1.2,1.1.3)',\ + org.apache.felix.gogo.runtime;version='[1.1.6,1.1.7)' \ No newline at end of file From aa9d8e8d6c0470be0a68d7f9db802e4228e077ad Mon Sep 17 00:00:00 2001 From: Tim Ward Date: Mon, 23 Sep 2024 11:17:56 +0100 Subject: [PATCH 09/12] [generics] Fix broken signatures in proxy classes Some generic mappings were creating partially valid signatures which terminated early, losing generics information for additional contracts. These tests and fix ensure that the signature is generated correctly Signed-off-by: Tim Ward --- .../rest/proxy/ExtensionProxyFactory.java | 80 ++++++++- .../rest/proxy/ExtensionProxyTest.java | 158 ++++++++++++++++++ 2 files changed, 232 insertions(+), 6 deletions(-) diff --git a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java index 14256b9..e21aea7 100644 --- a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java +++ b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java @@ -56,7 +56,11 @@ public class ExtensionProxyFactory { private static final String OBJECT_INTERNAL_NAME = Type.getInternalName(Object.class); /** - * @param simpleName + * Generate a proxy class which copies the signature of the delegate + * + * @param className - the name to use for the new class + * @param delegate - the object to proxy + * @param contracts - the extension contracts to honour */ public static byte[] generateClass(String className, Object delegate, List> contracts) { Class delegateClazz = delegate.getClass(); @@ -141,6 +145,15 @@ public static byte[] generateClass(String className, Object delegate, List toCheck, Map typeInfo, Map contextMapping, Set> remainingContracts) { if(toCheck == Object.class) { @@ -169,6 +182,16 @@ private static void populateInterfacesAndGenericSuperclasses(Classtv is used in the contracts + */ private static boolean isUsedInContracts(TypeVariable> tv, Map typeInfo, Map context, List> contracts) { BiPredicate contractUses = (varName, contextClass) -> contracts.stream() .filter(c -> contextClass.equals(context.get(c.getName()))) @@ -203,8 +226,17 @@ private static boolean isUsedInContracts(TypeVariable> tv, Map + * The syntax is available at + * but we use ASM's generator to help. + * + * @param delegateClazz - the class that we're creating a proxy for + * @param typeInfo - the known type name to type information mapping + * @param context - A mapping of type names to the class which defines them + * @param contracts - the interfaces that we're proxying + * @return the class signature */ private static String generateGenericClassSignature(Class delegateClazz, Map typeInfo, Map context, List> contracts) { if(typeInfo.isEmpty()) { @@ -250,6 +282,13 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< return writer.toString(); } + /** + * Find the reified type information, if any, for the supplied type variable + * + * @param t - the type to reify + * @param typeInfo - the known type name to type information mapping + * @param context - A mapping of type names to the class which defines them + */ private static java.lang.reflect.Type getPossibleReifiedTypeFor(TypeVariable tv, Map typeInfo, Map context) { Class declaringType = (Class) tv.getGenericDeclaration(); @@ -271,6 +310,11 @@ private static java.lang.reflect.Type getPossibleReifiedTypeFor(TypeVariable return tv; } + /** + * Maps a type variable to a stream of nested variables + * @param t the variable to map + * @return + */ private static Stream> toTypeVariables(java.lang.reflect.Type t) { if(t instanceof Class) { return Stream.empty(); @@ -284,6 +328,17 @@ private static Stream> toTypeVariables(java.lang.reflect.Type t) } } + /** + * Fill in the signature for the supplied type variable + *

+ * Note that we don't visit the end of any type parameter that we create. This is + * expected to be handled by the caller closing their SignatureVisitor + * + * @param t - the type to visit + * @param sv - the visitor to update with type information + * @param typeInfo - the known type name to type information mapping + * @param context - A mapping of type names to the class which defines them + */ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Map typeInfo, Map context) { if(t instanceof Class) { Class clazz = (Class) t; @@ -292,7 +347,7 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito } else if (clazz.isArray()) { SignatureVisitor av = sv.visitArrayType(); visitTypeParameter(clazz.getComponentType(), av, typeInfo, context); - av.visitEnd(); + // Do not visit the end } else { sv.visitClassType(Type.getInternalName(clazz)); } @@ -302,6 +357,7 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> { SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); visitTypeParameter(ta, tav, typeInfo, context); + // Here we must visit the end as we created a new class type context tav.visitEnd(); }); } else if (t instanceof TypeVariable) { @@ -312,7 +368,7 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito } else { SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); visitTypeParameter(t, tav, typeInfo, context); - tav.visitEnd(); + // Do not visit the end } } else if (t instanceof WildcardType) { WildcardType wt = (WildcardType) t; @@ -326,12 +382,22 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito types = wt.getUpperBounds(); } Arrays.stream(types).forEach(ty -> visitTypeParameter(ty, tav, typeInfo, context)); - tav.visitEnd(); + // Do not visit the end } else { throw new IllegalArgumentException("Unhandled generic type " + t.getClass() + " " + t.toString()); } } + /** + * Visit the member information of a defined annotation and write it into the + * class file. + * + * @param a - the annotation to copy from + * @param av - the visitor to copy into + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ private static void visitAnnotationMembers(Annotation a, AnnotationVisitor av) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { for (Method method : a.annotationType().getDeclaredMethods()) { Class returnType = method.getReturnType(); @@ -367,6 +433,8 @@ private static void visitAnnotationMembers(Annotation a, AnnotationVisitor av) t } /** + * Get the simple name for this generated class + * * @return */ public static String getSimpleName(Integer rank, Long id) { diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index ae57cad..6381d04 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -550,6 +550,100 @@ public void testMultiInterfaceMapper() throws Exception { } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testMultiInterfaceGenericComplex() throws Exception { + + TestMultiInterfaceGenericComplex mi = new TestMultiInterfaceGenericComplex<>(); + + // Deliberately re-order the interfaces relative to the implements clause + Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplex", mi, + Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); + + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); + assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(2, genericInterfaces.length); + + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(MessageBodyWriter.class, pt.getRawType()); + TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("W", tv.getName()); + assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); + + assertTrue(genericInterfaces[1] instanceof ParameterizedType); + pt = (ParameterizedType) genericInterfaces[1]; + assertEquals(MessageBodyReader.class, pt.getRawType()); + tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("R", tv.getName()); + assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> mi); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((MessageBodyWriter) instance).writeTo("ignore me", Object.class, null, null, null, null, baos); + + assertArrayEquals(new byte[]{0x42}, baos.toByteArray()); + + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + + assertEquals("tada", ((MessageBodyReader) instance).readFrom(Object.class, null, null, null, null, bais)); + + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testMultiInterfaceGenericComplexTwo() throws Exception { + + TestMultiInterfaceGenericComplexTwo mi = new TestMultiInterfaceGenericComplexTwo<>(); + + // Deliberately re-order the interfaces relative to the implements clause + Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplexTwo", mi, + Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); + + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); + assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(2, genericInterfaces.length); + + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(MessageBodyWriter.class, pt.getRawType()); + TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("W", tv.getName()); + assertArrayEquals(new Type[] {CharSequence.class}, tv.getBounds()); + + assertTrue(genericInterfaces[1] instanceof ParameterizedType); + pt = (ParameterizedType) genericInterfaces[1]; + assertEquals(MessageBodyReader.class, pt.getRawType()); + tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("R", tv.getName()); + assertArrayEquals(new Type[] {Number.class}, tv.getBounds()); + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> mi); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((MessageBodyWriter) instance).writeTo("banana", CharSequence.class, null, null, null, null, baos); + + // 4 characters, "anan" + assertArrayEquals(new byte[]{0,4,97,110,97,110}, baos.toByteArray()); + + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + + assertEquals(17, ((MessageBodyReader) instance).readFrom(Number.class, null, null, null, null, bais)); + + } + public static class TestExceptionMapper implements ExceptionMapper { /* @@ -739,7 +833,71 @@ public Integer readFrom(Class type, Type genericType, Annotation[] anno } } } + + public static abstract class BaseTestMultiInterfaceGenericCompelex + implements MessageBodyReader, MessageBodyWriter { + + } + + public static class TestMultiInterfaceGenericComplex + extends BaseTestMultiInterfaceGenericCompelex { + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return false; + } + + @Override + public void writeTo(W t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + entityStream.write(0x42); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return false; + } + + @SuppressWarnings("unchecked") + @Override + public R readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + return (R) "tada"; + } + } + + public static class TestMultiInterfaceGenericComplexTwo + extends BaseTestMultiInterfaceGenericCompelex { + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return false; + } + + @Override + public void writeTo(W t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + try(DataOutputStream daos = new DataOutputStream(entityStream)) { + daos.writeUTF(t.subSequence(1, t.length() - 1).toString()); + } + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return false; + } + + @SuppressWarnings("unchecked") + @Override + public R readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + return (R) Integer.valueOf(17); + } + } public static class PublicClassLoader extends ClassLoader { From 685c07f50ad8ed56cecba2cc220a928d7e9ce5d8 Mon Sep 17 00:00:00 2001 From: Juergen Albert Date: Mon, 23 Sep 2024 16:15:27 +0200 Subject: [PATCH 10/12] adds two extra tests and fixes generics issue Signed-off-by: Juergen Albert --- .../rest/proxy/ExtensionProxyFactory.java | 9 +- .../rest/proxy/ExtensionProxyTest.java | 799 +++++++++--------- 2 files changed, 407 insertions(+), 401 deletions(-) diff --git a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java index e21aea7..10da3ac 100644 --- a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java +++ b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java @@ -339,7 +339,7 @@ private static Stream> toTypeVariables(java.lang.reflect.Type t) * @param typeInfo - the known type name to type information mapping * @param context - A mapping of type names to the class which defines them */ - private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Map typeInfo, Map context) { + private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Map typeInfo, Map context) { if(t instanceof Class) { Class clazz = (Class) t; if(clazz.isPrimitive()) { @@ -350,6 +350,7 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito // Do not visit the end } else { sv.visitClassType(Type.getInternalName(clazz)); + return true; } } else if (t instanceof ParameterizedType){ ParameterizedType pt = (ParameterizedType) t; @@ -367,8 +368,9 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito sv.visitTypeArgument(SignatureVisitor.INSTANCEOF).visitTypeVariable(tv.getName()); } else { SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(t, tav, typeInfo, context); - // Do not visit the end + if(visitTypeParameter(t, tav, typeInfo, context)) { + tav.visitEnd(); + } } } else if (t instanceof WildcardType) { WildcardType wt = (WildcardType) t; @@ -386,6 +388,7 @@ private static void visitTypeParameter(java.lang.reflect.Type t, SignatureVisito } else { throw new IllegalArgumentException("Unhandled generic type " + t.getClass() + " " + t.toString()); } + return false; } /** diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index 6381d04..840677b 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -37,6 +37,7 @@ import java.util.Map; import java.util.function.Supplier; +import org.glassfish.jersey.internal.util.ReflectionHelper; import org.junit.jupiter.api.Test; import jakarta.ws.rs.NotFoundException; @@ -58,470 +59,419 @@ public class ExtensionProxyTest { PublicClassLoader pcl = new PublicClassLoader(); - + @Test public void testNameOrdering() { - assertEquals(ExtensionProxyFactory.getSimpleName(0, 1L), - ExtensionProxyFactory.getSimpleName(0, 1L)); - - assertTrue(ExtensionProxyFactory.getSimpleName(1, 1L) - .compareTo(ExtensionProxyFactory.getSimpleName(0, 2L)) < 0); - assertTrue(ExtensionProxyFactory.getSimpleName(0, 1L) - .compareTo(ExtensionProxyFactory.getSimpleName(-1, 2L)) < 0); - assertTrue(ExtensionProxyFactory.getSimpleName(0, 1L) - .compareTo(ExtensionProxyFactory.getSimpleName(0, 2L)) < 0); - assertTrue(ExtensionProxyFactory.getSimpleName(-1, 1L) - .compareTo(ExtensionProxyFactory.getSimpleName(0, 2L)) > 0); - assertTrue(ExtensionProxyFactory.getSimpleName(0, 1L) - .compareTo(ExtensionProxyFactory.getSimpleName(1, 2L)) > 0); - } - - + assertEquals(ExtensionProxyFactory.getSimpleName(0, 1L), ExtensionProxyFactory.getSimpleName(0, 1L)); + + assertTrue( + ExtensionProxyFactory.getSimpleName(1, 1L).compareTo(ExtensionProxyFactory.getSimpleName(0, 2L)) < 0); + assertTrue( + ExtensionProxyFactory.getSimpleName(0, 1L).compareTo(ExtensionProxyFactory.getSimpleName(-1, 2L)) < 0); + assertTrue( + ExtensionProxyFactory.getSimpleName(0, 1L).compareTo(ExtensionProxyFactory.getSimpleName(0, 2L)) < 0); + assertTrue( + ExtensionProxyFactory.getSimpleName(-1, 1L).compareTo(ExtensionProxyFactory.getSimpleName(0, 2L)) > 0); + assertTrue( + ExtensionProxyFactory.getSimpleName(0, 1L).compareTo(ExtensionProxyFactory.getSimpleName(1, 2L)) > 0); + } + @SuppressWarnings("unchecked") @Test public void testExceptionMapper() throws Exception { - + ExceptionMapper em = new TestExceptionMapper(); - - Class proxyClazz = pcl.define("test.ExceptionMapper", em, - singletonList(ExceptionMapper.class)); - + + Class proxyClazz = pcl.define("test.ExceptionMapper", em, singletonList(ExceptionMapper.class)); + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ExceptionMapper.class, pt.getRawType()); assertEquals(NullPointerException.class, pt.getActualTypeArguments()[0]); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> em); - - assertEquals(418, ((ExceptionMapper)instance) - .toResponse(new NullPointerException()).getStatus()); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> em); + + assertEquals(418, + ((ExceptionMapper) instance).toResponse(new NullPointerException()).getStatus()); + } @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void testRawExceptionMapper() throws Exception { - + ExceptionMapper em = new RawExceptionMapper(); - - Class proxyClazz = pcl.define("test.RawExceptionMapper", em, - singletonList(ExceptionMapper.class)); - + + Class proxyClazz = pcl.define("test.RawExceptionMapper", em, singletonList(ExceptionMapper.class)); + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertEquals(ExceptionMapper.class, genericInterfaces[0]); - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> em); - - assertEquals(814, ((ExceptionMapper)instance) - .toResponse(new OutOfMemoryError()).getStatus()); - - } - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> em); + + assertEquals(814, ((ExceptionMapper) instance).toResponse(new OutOfMemoryError()).getStatus()); + + } + @SuppressWarnings("unchecked") @Test public void testParameterExceptionMapper() throws Exception { - + ExceptionMapper em = new ParameterExceptionMapper<>(); - - Class proxyClazz = pcl.define("test.ExceptionMapper", em, - singletonList(ExceptionMapper.class)); - + + Class proxyClazz = pcl.define("test.ExceptionMapper", em, singletonList(ExceptionMapper.class)); + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); - + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(1, typeParameters.length); assertTrue(TypeVariable.class.isInstance(typeParameters[0])); assertEquals("T", typeParameters[0].toString()); - Type[] bounds = ((TypeVariable)typeParameters[0]).getBounds(); + Type[] bounds = ((TypeVariable) typeParameters[0]).getBounds(); assertEquals(1, bounds.length); assertEquals(Throwable.class, bounds[0]); - + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ExceptionMapper.class, pt.getRawType()); assertTrue(TypeVariable.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("T", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> em); - - assertEquals(451, ((ExceptionMapper)instance) - .toResponse(new RuntimeException()).getStatus()); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> em); + + assertEquals(451, ((ExceptionMapper) instance).toResponse(new RuntimeException()).getStatus()); + } @SuppressWarnings("unchecked") @Test public void testChildReifiedExceptionMapper() throws Exception { - + ExceptionMapper em = new ChildReifiedExceptionMapper(); - - Class proxyClazz = pcl.define("test.ExceptionMapper", em, - singletonList(ExceptionMapper.class)); - + + Class proxyClazz = pcl.define("test.ExceptionMapper", em, singletonList(ExceptionMapper.class)); + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); - + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(0, typeParameters.length); - + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ExceptionMapper.class, pt.getRawType()); assertEquals(WebApplicationException.class, pt.getActualTypeArguments()[0]); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> em); - - assertEquals(404, ((ExceptionMapper)instance) - .toResponse(new NotFoundException()).getStatus()); - - } - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> em); + + assertEquals(404, + ((ExceptionMapper) instance).toResponse(new NotFoundException()).getStatus()); + + } + @SuppressWarnings("unchecked") @Test public void testParameterContext() throws Exception { - + ContextResolver cr = new ParameterContext<>(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); - + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(1, typeParameters.length); assertTrue(TypeVariable.class.isInstance(typeParameters[0])); assertEquals("T", typeParameters[0].toString()); - Type[] bounds = ((TypeVariable)typeParameters[0]).getBounds(); + Type[] bounds = ((TypeVariable) typeParameters[0]).getBounds(); assertEquals(1, bounds.length); assertEquals(Object.class, bounds[0]); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(TypeVariable.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("T", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals("", ((ContextResolver)instance) - .getContext(String.class)); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals("", ((ContextResolver) instance).getContext(String.class)); + } @SuppressWarnings("unchecked") @Test public void testExtendsParameterContext() throws Exception { - + ContextResolver> cr = new ExtendsParameterContext<>(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); - + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(1, typeParameters.length); assertTrue(TypeVariable.class.isInstance(typeParameters[0])); assertEquals("R", typeParameters[0].toString()); - Type[] bounds = ((TypeVariable)typeParameters[0]).getBounds(); + Type[] bounds = ((TypeVariable) typeParameters[0]).getBounds(); assertEquals(1, bounds.length); assertEquals(Number.class, bounds[0]); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals(Collections.emptyList(), ((ContextResolver>)instance) - .getContext(String.class)); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals(Collections.emptyList(), ((ContextResolver>) instance).getContext(String.class)); + } @SuppressWarnings("unchecked") @Test public void testChildExtendsParameterContext() throws Exception { - + ContextResolver> cr = new ChildExtendsParameterContext(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); - + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(0, typeParameters.length); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals(Collections.singletonList(42.0d), ((ContextResolver>)instance) - .getContext(String.class)); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals(Collections.singletonList(42.0d), + ((ContextResolver>) instance).getContext(String.class)); + } @SuppressWarnings("unchecked") @Test public void testWildcardParameterContext() throws Exception { - + ContextResolver> cr = new WildcardParameterContext(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals(Collections.emptyList(), ((ContextResolver>)instance) - .getContext(String.class)); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals(Collections.emptyList(), ((ContextResolver>) instance).getContext(String.class)); + } @Test public void testRedirectsParameterContext() throws Exception { - + ContextResolver cr = new RedirectsParameterContext<>(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); - + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(1, typeParameters.length); assertTrue(TypeVariable.class.isInstance(typeParameters[0])); assertEquals("R", typeParameters[0].toString()); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(TypeVariable.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("R", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals("", ((ContextResolver)instance) - .getContext(String.class)); - - } - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals("", ((ContextResolver) instance).getContext(String.class)); + + } + @Test public void testChildExtendsRedirectedParameterContext() throws Exception { - + ContextResolver cr = new ChildExtendsRedirectedParameterContext(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(0, typeParameters.length); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertEquals(Double.class, pt.getActualTypeArguments()[0]); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals(42.0d, ((ContextResolver)instance) - .getContext(String.class)); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals(42.0d, ((ContextResolver) instance).getContext(String.class)); + } + @Test public void testChildExtraExtendsRedirectedParameterContext() throws Exception { - + ContextResolver> cr = new ChildExtraExtendsRedirectedParameterContext(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("java.util.List", pt.getActualTypeArguments()[0].getTypeName()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals(Collections.singletonList(17.0d), ((ContextResolver)instance) - .getContext(String.class)); - - } - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals(Collections.singletonList(17.0d), ((ContextResolver) instance).getContext(String.class)); + + } + @Test public void testIndirectlyRedirectsParameterContext() throws Exception { - + ContextResolver cr = new IndirectlyRedirectsParameterContext<>(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); - + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(1, typeParameters.length); assertTrue(TypeVariable.class.isInstance(typeParameters[0])); assertEquals("R", typeParameters[0].toString()); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); assertEquals("java.util.List", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals("", ((ContextResolver)instance) - .getContext(String.class)); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals("", ((ContextResolver) instance).getContext(String.class)); + } @Test public void testChildExtraExtendsIndirectlyRedirectsParameterContext() throws Exception { - + ContextResolver cr = new ChildExtraExtendsIndirectlyRedirectedParameterContext(); - - Class proxyClazz = pcl.define("test.ContextResolver", cr, - singletonList(ContextResolver.class)); - + + Class proxyClazz = pcl.define("test.ContextResolver", cr, singletonList(ContextResolver.class)); + TypeVariable[] typeParameters = proxyClazz.getTypeParameters(); assertEquals(0, typeParameters.length); - + assertTrue(ContextResolver.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ContextResolver.class, pt.getRawType()); assertTrue(ParameterizedType.class.isInstance(pt.getActualTypeArguments()[0])); - assertEquals("java.util.List>", pt.getActualTypeArguments()[0].toString()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> cr); - - assertEquals(Collections.singletonList(Collections.singletonMap("foo", 42)), ((ContextResolver)instance) - .getContext(String.class)); - - } - + assertEquals("java.util.List>", + pt.getActualTypeArguments()[0].toString()); + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> cr); + + assertEquals(Collections.singletonList(Collections.singletonMap("foo", 42)), + ((ContextResolver) instance).getContext(String.class)); + + } + @SuppressWarnings("unchecked") @Test public void testAnnotatedExceptionMapper() throws Exception { - + ExceptionMapper em = new AnnotatedExceptionMapper(); - - Class proxyClazz = pcl.define("test.AnnotatedExceptionMapper", em, - singletonList(ExceptionMapper.class)); - + + Class proxyClazz = pcl.define("test.AnnotatedExceptionMapper", em, singletonList(ExceptionMapper.class)); + assertTrue(ExceptionMapper.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(1, genericInterfaces.length); assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(ExceptionMapper.class, pt.getRawType()); assertEquals(IllegalArgumentException.class, pt.getActualTypeArguments()[0]); - - + assertEquals(1, proxyClazz.getAnnotations().length); assertNotNull(proxyClazz.getAnnotation(Path.class)); - + Path path = proxyClazz.getAnnotation(Path.class); assertEquals("boo", path.value()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> em); - - assertEquals(777, ((ExceptionMapper)instance) + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> em); + + assertEquals(777, ((ExceptionMapper) instance) .toResponse(new IllegalArgumentException()).getStatus()); - + } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testMultiInterfaceMapper() throws Exception { - + TestMultiInterface mi = new TestMultiInterface(); - + // Deliberately re-order the interfaces relative to the implements clause - Class proxyClazz = pcl.define("test.MultiInterface", mi, + Class proxyClazz = pcl.define("test.MultiInterface", mi, Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); - + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - + assertEquals(2, genericInterfaces.length); - + assertTrue(genericInterfaces[0] instanceof ParameterizedType); ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; assertEquals(MessageBodyWriter.class, pt.getRawType()); @@ -531,123 +481,100 @@ public void testMultiInterfaceMapper() throws Exception { pt = (ParameterizedType) genericInterfaces[1]; assertEquals(MessageBodyReader.class, pt.getRawType()); assertEquals(Integer.class, pt.getActualTypeArguments()[0]); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> mi); - + + Object instance = proxyClazz.getConstructor(Supplier.class).newInstance((Supplier) () -> mi); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); ((MessageBodyWriter) instance).writeTo(42L, Long.class, null, null, null, null, baos); - - // 2 characters, "2a" - assertArrayEquals(new byte[]{0,2,50,97}, baos.toByteArray()); - - + + // 2 characters, "2a" + assertArrayEquals(new byte[] { 0, 2, 50, 97 }, baos.toByteArray()); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - - + assertEquals(42, ((MessageBodyReader) instance).readFrom(Integer.class, null, null, null, null, bais)); - + } - @SuppressWarnings({ "unchecked", "rawtypes" }) @Test - public void testMultiInterfaceGenericComplex() throws Exception { - - TestMultiInterfaceGenericComplex mi = new TestMultiInterfaceGenericComplex<>(); - - // Deliberately re-order the interfaces relative to the implements clause - Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplex", mi, - Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); - - assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); - assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); - Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - - assertEquals(2, genericInterfaces.length); - - assertTrue(genericInterfaces[0] instanceof ParameterizedType); - ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; - assertEquals(MessageBodyWriter.class, pt.getRawType()); - TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; - assertEquals("W", tv.getName()); - assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); - - assertTrue(genericInterfaces[1] instanceof ParameterizedType); - pt = (ParameterizedType) genericInterfaces[1]; - assertEquals(MessageBodyReader.class, pt.getRawType()); - tv = (TypeVariable) pt.getActualTypeArguments()[0]; - assertEquals("R", tv.getName()); - assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); - - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> mi); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ((MessageBodyWriter) instance).writeTo("ignore me", Object.class, null, null, null, null, baos); - - assertArrayEquals(new byte[]{0x42}, baos.toByteArray()); - - - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - - - assertEquals("tada", ((MessageBodyReader) instance).readFrom(Object.class, null, null, null, null, bais)); - + public void testMultiInterfaceMapperJerseyStyle() throws Exception { + + TestMultiInterfaceGenericComplexConcret mi = new TestMultiInterfaceGenericComplexConcret(); + + ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) { + + /* + * (non-Javadoc) + * + * @see java.lang.ClassLoader#findClass(java.lang.String) + */ + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] b = ExtensionProxyFactory.generateClass("test.proxy.MultiExtenstion", mi, + List.of(MessageBodyReader.class, MessageBodyWriter.class)); + return defineClass(name, b, 0, b.length, mi.getClass().getProtectionDomain()); + } + }; + + Class class1 = classLoader.loadClass("test.proxy.MultiExtenstion"); + assertNotNull(class1); + + ReflectionHelper.DeclaringClassInterfacePair pair1 = ReflectionHelper.getClass(class1, MessageBodyWriter.class); + assertNotNull(pair1); + + ReflectionHelper.DeclaringClassInterfacePair pair2 = ReflectionHelper.getClass(class1, MessageBodyReader.class); + assertNotNull(pair2); } - @SuppressWarnings({ "unchecked", "rawtypes" }) @Test - public void testMultiInterfaceGenericComplexTwo() throws Exception { - - TestMultiInterfaceGenericComplexTwo mi = new TestMultiInterfaceGenericComplexTwo<>(); - - // Deliberately re-order the interfaces relative to the implements clause - Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplexTwo", mi, - Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); - - assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); - assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); - Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); - - assertEquals(2, genericInterfaces.length); - - assertTrue(genericInterfaces[0] instanceof ParameterizedType); - ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; - assertEquals(MessageBodyWriter.class, pt.getRawType()); - TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; - assertEquals("W", tv.getName()); - assertArrayEquals(new Type[] {CharSequence.class}, tv.getBounds()); - - assertTrue(genericInterfaces[1] instanceof ParameterizedType); - pt = (ParameterizedType) genericInterfaces[1]; - assertEquals(MessageBodyReader.class, pt.getRawType()); - tv = (TypeVariable) pt.getActualTypeArguments()[0]; - assertEquals("R", tv.getName()); - assertArrayEquals(new Type[] {Number.class}, tv.getBounds()); - - Object instance = proxyClazz.getConstructor(Supplier.class) - .newInstance((Supplier) () -> mi); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ((MessageBodyWriter) instance).writeTo("banana", CharSequence.class, null, null, null, null, baos); - - // 4 characters, "anan" - assertArrayEquals(new byte[]{0,4,97,110,97,110}, baos.toByteArray()); - - - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - - - assertEquals(17, ((MessageBodyReader) instance).readFrom(Number.class, null, null, null, null, bais)); - + public void testMultiInterfaceMapperJerseyStyleGeneric() throws Exception { + + TestMultiInterfaceGenericComplex mi = new TestMultiInterfaceGenericComplex(); + + // Test Plain + + ReflectionHelper.DeclaringClassInterfacePair pair1 = ReflectionHelper.getClass(mi.getClass(), + MessageBodyWriter.class); + assertNotNull(pair1); + + ReflectionHelper.DeclaringClassInterfacePair pair2 = ReflectionHelper.getClass(mi.getClass(), + MessageBodyReader.class); + assertNotNull(pair2); + + ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) { + + /* + * (non-Javadoc) + * + * @see java.lang.ClassLoader#findClass(java.lang.String) + */ + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] b = ExtensionProxyFactory.generateClass("test.proxy.MultiExtenstion", mi, + List.of(MessageBodyReader.class, MessageBodyWriter.class)); + return defineClass(name, b, 0, b.length, mi.getClass().getProtectionDomain()); + } + }; + + Class class1 = classLoader.loadClass("test.proxy.MultiExtenstion"); + assertNotNull(class1); + + final Type[] gis = class1.getGenericInterfaces(); + + assertEquals(2, gis.length); + + pair1 = ReflectionHelper.getClass(class1, MessageBodyWriter.class); + assertNotNull(pair1); + + pair2 = ReflectionHelper.getClass(class1, MessageBodyReader.class); + assertNotNull(pair2); } public static class TestExceptionMapper implements ExceptionMapper { - /* + /* * (non-Javadoc) + * * @see jakarta.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) */ @Override @@ -655,14 +582,15 @@ public Response toResponse(NullPointerException exception) { // I'm a teapot return Response.status(418).build(); } - + } @SuppressWarnings("rawtypes") public static class RawExceptionMapper implements ExceptionMapper { - - /* + + /* * (non-Javadoc) + * * @see jakarta.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) */ @Override @@ -670,13 +598,14 @@ public Response toResponse(Throwable exception) { // What's an 8xx response?!? return Response.status(814).build(); } - + } public static class ParameterExceptionMapper implements ExceptionMapper { - - /* + + /* * (non-Javadoc) + * * @see jakarta.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) */ @Override @@ -684,16 +613,16 @@ public Response toResponse(T exception) { // Unavailable for legal reasons return Response.status(451).build(); } - + } - + public static class ChildReifiedExceptionMapper extends ParameterExceptionMapper { @Override public Response toResponse(WebApplicationException exception) { return Response.status(exception.getResponse().getStatus()).build(); } - + } public static class ParameterContext implements ContextResolver { @@ -707,76 +636,78 @@ public T getContext(Class type) { throw new RuntimeException(e); } } - + } public static class ExtendsParameterContext implements ContextResolver> { - + @Override public List getContext(Class type) { return new ArrayList<>(); } - + } public static class ChildExtendsParameterContext extends ExtendsParameterContext { - + @Override public List getContext(Class type) { return Collections.singletonList(42.0d); } - + } public static class WildcardParameterContext implements ContextResolver> { - + @Override public List getContext(Class type) { return new ArrayList<>(); } - + } - + public static class RedirectsParameterContext extends ParameterContext { - + } - + public static class ChildExtendsRedirectedParameterContext extends RedirectsParameterContext { - + @Override public Double getContext(Class type) { return 42.0d; } - + } public static class ChildExtraExtendsRedirectedParameterContext extends RedirectsParameterContext> { - + @Override public List getContext(Class type) { return Collections.singletonList(17.0d); } - + } - + public static class IndirectlyRedirectsParameterContext extends ParameterContext> { - + } - - public static class ChildExtraExtendsIndirectlyRedirectedParameterContext extends IndirectlyRedirectsParameterContext> { - + + public static class ChildExtraExtendsIndirectlyRedirectedParameterContext + extends IndirectlyRedirectsParameterContext> { + @Override public List> getContext(Class type) { return Collections.singletonList(Collections.singletonMap("foo", 42)); } - + } @Path("boo") public static class AnnotatedExceptionMapper implements ExceptionMapper { - - /* + + /* * (non-Javadoc) + * * @see jakarta.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) */ @Override @@ -784,45 +715,59 @@ public Response toResponse(IllegalArgumentException exception) { // All lucky 7s return Response.status(777).build(); } - + } public static class TestMultiInterface implements MessageBodyReader, MessageBodyWriter { - /* + /* * (non-Javadoc) - * @see jakarta.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType) + * + * @see jakarta.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class, + * java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType) */ @Override public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return false; } - /* + /* * (non-Javadoc) - * @see jakarta.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, java.io.OutputStream) + * + * @see jakarta.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, + * java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, + * java.io.OutputStream) */ @Override public void writeTo(Long t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - try(DataOutputStream daos = new DataOutputStream(entityStream)) { + try (DataOutputStream daos = new DataOutputStream(entityStream)) { daos.writeUTF(Long.toHexString(t)); } } - /* + /* * (non-Javadoc) - * @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType) + * + * @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, + * java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType) */ @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return false; } - /* + /* * (non-Javadoc) - * @see jakarta.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, java.io.InputStream) + * + * @see jakarta.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, + * java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, + * java.io.InputStream) */ @Override public Integer readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, @@ -835,13 +780,13 @@ public Integer readFrom(Class type, Type genericType, Annotation[] anno } public static abstract class BaseTestMultiInterfaceGenericCompelex - implements MessageBodyReader, MessageBodyWriter { + implements MessageBodyReader, MessageBodyWriter { } public static class TestMultiInterfaceGenericComplex - extends BaseTestMultiInterfaceGenericCompelex { - + extends BaseTestMultiInterfaceGenericCompelex { + @Override public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return false; @@ -869,38 +814,96 @@ public R readFrom(Class type, Type genericType, Annotation[] annotations, Med } public static class TestMultiInterfaceGenericComplexTwo - extends BaseTestMultiInterfaceGenericCompelex { - + extends BaseTestMultiInterfaceGenericCompelex { + @Override public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return false; } - + @Override public void writeTo(W t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) - throws IOException, WebApplicationException { - try(DataOutputStream daos = new DataOutputStream(entityStream)) { + throws IOException, WebApplicationException { + try (DataOutputStream daos = new DataOutputStream(entityStream)) { daos.writeUTF(t.subSequence(1, t.length() - 1).toString()); } } - + @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return false; } - + @SuppressWarnings("unchecked") @Override public R readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) - throws IOException, WebApplicationException { + throws IOException, WebApplicationException { return (R) Integer.valueOf(17); } } - + + public static class TestMultiInterfaceGenericComplexConcret + extends BaseTestMultiInterfaceGenericCompelex { + + /* + * (non-Javadoc) + * + * @see jakarta.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class, + * java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType) + */ + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return false; + } + + /* + * (non-Javadoc) + * + * @see jakarta.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, + * java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, + * java.io.OutputStream) + */ + @Override + public void writeTo(String t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + + } + + /* + * (non-Javadoc) + * + * @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, + * java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType) + */ + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return false; + } + + /* + * (non-Javadoc) + * + * @see jakarta.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, + * java.lang.reflect.Type, java.lang.annotation.Annotation[], + * jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, + * java.io.InputStream) + */ + @Override + public String readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + return "tada"; + } + } + public static class PublicClassLoader extends ClassLoader { - + public Class define(String name, Object delegate, List> contracts) { byte[] b = ExtensionProxyFactory.generateClass(name, delegate, contracts); return defineClass(name, b, 0, b.length); From 91d582bc4f54a51e11eb5a6c836cb15a3b35b251 Mon Sep 17 00:00:00 2001 From: Tim Ward Date: Mon, 23 Sep 2024 16:48:27 +0100 Subject: [PATCH 11/12] [generics] Restore deleted tests and improve consistency of access The latest commit removed some tests which need to be restored. It also made some visitor usage inconsistent, so we now respect the return value of visitTypeParameter everywhere Signed-off-by: Tim Ward --- .../rest/proxy/ExtensionProxyFactory.java | 32 ++++--- .../rest/proxy/ExtensionProxyTest.java | 94 +++++++++++++++++++ 2 files changed, 115 insertions(+), 11 deletions(-) diff --git a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java index 10da3ac..e127d22 100644 --- a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java +++ b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java @@ -251,6 +251,8 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< .forEach(tv -> { writer.visitFormalTypeParameter(tv.getName()); SignatureVisitor cb = writer.visitClassBound(); + // Class bounds are special and do not require a visitEnd + // even if a new class context is set Arrays.stream(tv.getBounds()).forEach(b -> visitTypeParameter(b, cb, typeInfo, context)); cb.visitEnd(); }); @@ -266,12 +268,14 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< SignatureVisitor iv = writer.visitInterface(); iv.visitClassType(Type.getInternalName(contract)); for(java.lang.reflect.Type t : typeInfo.get(contract.getName()).getActualTypeArguments()) { + SignatureVisitor v; if(TypeVariable.class.isInstance(t)) { - visitTypeParameter(t, iv, typeInfo, context); + v = iv; } else { - SignatureVisitor tav = iv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(t, tav, typeInfo, context); - tav.visitEnd(); + v = iv.visitTypeArgument(SignatureVisitor.INSTANCEOF); + } + if(visitTypeParameter(t, v, typeInfo, context)) { + v.visitEnd(); } } iv.visitEnd(); @@ -338,6 +342,7 @@ private static Stream> toTypeVariables(java.lang.reflect.Type t) * @param sv - the visitor to update with type information * @param typeInfo - the known type name to type information mapping * @param context - A mapping of type names to the class which defines them + * @return true if a new class type has been established and an additional close is needed */ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Map typeInfo, Map context) { if(t instanceof Class) { @@ -346,8 +351,9 @@ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVis sv.visitBaseType(Type.getDescriptor(clazz).charAt(0)); } else if (clazz.isArray()) { SignatureVisitor av = sv.visitArrayType(); - visitTypeParameter(clazz.getComponentType(), av, typeInfo, context); - // Do not visit the end + if(visitTypeParameter(clazz.getComponentType(), av, typeInfo, context)) { + av.visitEnd(); + } } else { sv.visitClassType(Type.getInternalName(clazz)); return true; @@ -357,10 +363,11 @@ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVis sv.visitClassType(Type.getInternalName((Class)pt.getRawType())); Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> { SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(ta, tav, typeInfo, context); - // Here we must visit the end as we created a new class type context - tav.visitEnd(); + if(visitTypeParameter(ta, tav, typeInfo, context)) { + tav.visitEnd(); + } }); + return true; } else if (t instanceof TypeVariable) { TypeVariable tv = (TypeVariable) t; t = getPossibleReifiedTypeFor((TypeVariable)t, typeInfo, context); @@ -383,8 +390,11 @@ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVis tav = sv.visitTypeArgument(SignatureVisitor.EXTENDS); types = wt.getUpperBounds(); } - Arrays.stream(types).forEach(ty -> visitTypeParameter(ty, tav, typeInfo, context)); - // Do not visit the end + Arrays.stream(types).forEach(ty -> { + if(visitTypeParameter(ty, tav, typeInfo, context)) { + tav.visitEnd(); + } + }); } else { throw new IllegalArgumentException("Unhandled generic type " + t.getClass() + " " + t.toString()); } diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index 840677b..22cd79f 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -496,6 +496,100 @@ public void testMultiInterfaceMapper() throws Exception { } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testMultiInterfaceGenericComplex() throws Exception { + + TestMultiInterfaceGenericComplex mi = new TestMultiInterfaceGenericComplex<>(); + + // Deliberately re-order the interfaces relative to the implements clause + Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplex", mi, + Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); + + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); + assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(2, genericInterfaces.length); + + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(MessageBodyWriter.class, pt.getRawType()); + TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("W", tv.getName()); + assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); + + assertTrue(genericInterfaces[1] instanceof ParameterizedType); + pt = (ParameterizedType) genericInterfaces[1]; + assertEquals(MessageBodyReader.class, pt.getRawType()); + tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("R", tv.getName()); + assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> mi); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((MessageBodyWriter) instance).writeTo("ignore me", Object.class, null, null, null, null, baos); + + assertArrayEquals(new byte[]{0x42}, baos.toByteArray()); + + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + + assertEquals("tada", ((MessageBodyReader) instance).readFrom(Object.class, null, null, null, null, bais)); + + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testMultiInterfaceGenericComplexTwo() throws Exception { + + TestMultiInterfaceGenericComplexTwo mi = new TestMultiInterfaceGenericComplexTwo<>(); + + // Deliberately re-order the interfaces relative to the implements clause + Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplexTwo", mi, + Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); + + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); + assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(2, genericInterfaces.length); + + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(MessageBodyWriter.class, pt.getRawType()); + TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("W", tv.getName()); + assertArrayEquals(new Type[] {CharSequence.class}, tv.getBounds()); + + assertTrue(genericInterfaces[1] instanceof ParameterizedType); + pt = (ParameterizedType) genericInterfaces[1]; + assertEquals(MessageBodyReader.class, pt.getRawType()); + tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("R", tv.getName()); + assertArrayEquals(new Type[] {Number.class}, tv.getBounds()); + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> mi); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((MessageBodyWriter) instance).writeTo("banana", CharSequence.class, null, null, null, null, baos); + + // 4 characters, "anan" + assertArrayEquals(new byte[]{0,4,97,110,97,110}, baos.toByteArray()); + + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + + assertEquals(17, ((MessageBodyReader) instance).readFrom(Number.class, null, null, null, null, bais)); + + } + @Test public void testMultiInterfaceMapperJerseyStyle() throws Exception { From 1fef9ebaa673d7f09f8c826018ffc8822af58c28 Mon Sep 17 00:00:00 2001 From: Juergen Albert Date: Mon, 23 Sep 2024 18:18:23 +0200 Subject: [PATCH 12/12] fixes library build Signed-off-by: Juergen Albert --- org.eclipse.osgitech.rest.bnd.library/bnd.bnd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.eclipse.osgitech.rest.bnd.library/bnd.bnd b/org.eclipse.osgitech.rest.bnd.library/bnd.bnd index f14c706..2cd2ad6 100644 --- a/org.eclipse.osgitech.rest.bnd.library/bnd.bnd +++ b/org.eclipse.osgitech.rest.bnd.library/bnd.bnd @@ -1,5 +1,7 @@ -resourceonly: true +-includeresource: {library/workspace/jakartarest.mvn=src/main/resources/library/workspace/jakartarest.mvn} + jersey.version: 3.1.3 hk2.version: 3.0.5