From 1af09003cdec3f394a3d5b55398ea9b3a8dddeb7 Mon Sep 17 00:00:00 2001 From: Tim Ward Date: Mon, 26 Feb 2024 14:37:08 +0000 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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