You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2021/04/28 21:19:33 UTC

[tomee] branch master updated: Port of CXF-8531 Provider class sorting requires rework

This is an automated email from the ASF dual-hosted git repository.

dblevins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomee.git


The following commit(s) were added to refs/heads/master by this push:
     new 2b5f789  Port of CXF-8531 Provider class sorting requires rework
2b5f789 is described below

commit 2b5f789cce704b4f6c1a8a9a5aadf14f4b509e09
Author: David Blevins <da...@gmail.com>
AuthorDate: Wed Apr 28 13:52:50 2021 -0700

    Port of CXF-8531 Provider class sorting requires rework
---
 .../jaxrs/provider/GenericArgumentComparator.java  | 206 +++++++++++++++++++
 .../apache/cxf/jaxrs/provider/ProviderFactory.java |  50 +++--
 .../org/apache/cxf/jaxrs/utils/GenericsUtils.java  | 224 +++++++++++++++++++++
 3 files changed, 465 insertions(+), 15 deletions(-)

diff --git a/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/GenericArgumentComparator.java b/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/GenericArgumentComparator.java
new file mode 100644
index 0000000..505acfa
--- /dev/null
+++ b/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/GenericArgumentComparator.java
@@ -0,0 +1,206 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.provider;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Comparator;
+
+import org.apache.cxf.jaxrs.utils.GenericsUtils;
+
+public class GenericArgumentComparator implements Comparator<Class<?>> {
+
+    private final Class<?> genericInterface;
+
+    public GenericArgumentComparator(final Class<?> genericInterface) {
+        if (genericInterface == null) {
+            throw new IllegalArgumentException("Generic Interface cannot be null");
+        }
+        if (genericInterface.getTypeParameters().length == 0) {
+            throw new IllegalArgumentException("Interface has no generic type parameters: " + genericInterface);
+        }
+        if (genericInterface.getTypeParameters().length > 1) {
+            throw new IllegalArgumentException("Interface must have only 1 generic type parameter: "
+                    + genericInterface);
+        }
+        this.genericInterface = genericInterface;
+    }
+
+    /**
+     * This comparator sorts the most specific type to the top of the list.
+     *
+     * Effectively, this sorts classes in descending order with java.lang.Object
+     * always as the last element if present.
+     */
+    @Override
+    public int compare(final Class<?> a, final Class<?> b) {
+        /*
+         * To keep things from being too abstract and confusing, this javadoc refers
+         * MessageBodyReader as the value of genericInterface.
+         *
+         * It could be any similar interface with just one generic type parameter,
+         * such as MessageBodyWriter, ContextResolver, Consumer, etc.
+         */
+
+        /*
+         * Get the actual type each class specified as its MessageBodyReader generic
+         * parameter.  An array of one arg will be returned or null if the class does
+         * not implement MessageBodyReader.
+         */
+        final Type[] aTypes = GenericsUtils.getTypeArgumentsFor(genericInterface, a);
+        final Type[] bTypes = GenericsUtils.getTypeArgumentsFor(genericInterface, b);
+
+        /*
+         * If either class does not implement the MessageBodyReader interface and
+         * therefore returned a null Types array, that class should have the lower
+         * priority.
+         */
+        if (aTypes == bTypes) {
+            return 0;
+        }
+        if (aTypes == null) {
+            return 1;
+        }
+        if (bTypes == null) {
+            return -1;
+        }
+
+        /*
+         * We only support interfaces like MessageBodyReader that have
+         * just one type parameter.  Neither class returned a null Type
+         * array so we know each properly implements the interface and
+         * therefore we don't need to check array lengths.
+         */
+        final Type aType = aTypes[0];
+        final Type bType = bTypes[0];
+
+        return compare(aType, bType);
+    }
+
+    public int compare(final Type aType, final Type bType) {
+        if (aType == bType) {
+            return 0;
+        }
+
+        /*
+         * At this point we're now dealing with actual the value each
+         * class specified for their MessageBodyReader implementations.
+         *
+         * Types like String, Boolean and URI will appear as a Class.
+         * Types like JAXBElement which themselves have a parameter will
+         * appear as a ParameterizedType.
+         *
+         * Let's first evaluate them as basic classes.  Only if they're
+         * the same class do we need to look at their parameters.
+         */
+        final Class<?> aClass = asClass(aType);
+        final Class<?> bClass = asClass(bType);
+
+        /*
+         * If they aren't the same class we only need to look at the
+         * classes themselves and can ignore any parameters they have
+         */
+        if (!aClass.equals(bClass)) {
+            /*
+             * For those who can't remember this cryptic method:
+             *
+             * Red.class.isAssignableFrom(Color.class) == false
+             * Color.class.isAssignableFrom(Red.class) == true
+             */
+
+            // bClass is a more generic version of aClass
+            if (bClass.isAssignableFrom(aClass)) {
+                return -1;
+            }
+
+            // aClass is a more generic version of bClass
+            if (aClass.isAssignableFrom(bClass)) {
+                return 1;
+            }
+
+            // These classes are unrelated
+            return 0;
+        }
+
+        /*
+         * They are the same class, so let's look at their parameters
+         * and try to sort based on those.
+         */
+        final Type aParam = getFirstParameterOrObject(aType);
+        final Type bParam = getFirstParameterOrObject(bType);
+
+        return compare(aParam, bParam);
+    }
+
+    private Type getFirstParameterOrObject(final Type type) {
+        if (!(type instanceof ParameterizedType)) {
+            return Object.class;
+        }
+
+        final ParameterizedType parameterizedType = (ParameterizedType) type;
+        final Type[] types = parameterizedType.getActualTypeArguments();
+
+        if (types.length == 0) {
+            return Object.class;
+        }
+
+        /*
+         * This parameterized type may have more than one
+         * generic argument (like Map or Function do).  If
+         * so, too bad, we're ignoring it out of laziness.
+         *
+         * Feel free to come here and implement what makes
+         * sense to you if you need this feature.  Maybe
+         * you have a Map<String,Object> and Map<String,URL>
+         * situation you want to support.
+         */
+        return types[0];
+    }
+
+    private Class<?> asClass(final Type aType) {
+        if (aType instanceof Class) {
+            return (Class<?>) aType;
+        }
+
+        if (aType instanceof ParameterizedType) {
+            final ParameterizedType parameterizedType = (ParameterizedType) aType;
+            return asClass(parameterizedType.getRawType());
+        }
+
+        if (aType instanceof TypeVariable) {
+            final TypeVariable typeVariable = (TypeVariable) aType;
+            final Type[] bounds = typeVariable.getBounds();
+
+            if (bounds == null || bounds.length == 0) {
+                return Object.class;
+            } else {
+                return asClass(bounds[0]);
+            }
+        }
+
+        if (aType instanceof WildcardType) {
+            // todo
+            return Object.class;
+        }
+
+        return Object.class;
+    }
+}
diff --git a/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java b/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
index 4b16c1f..27f575a 100644
--- a/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
+++ b/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
@@ -154,10 +154,6 @@ public abstract class ProviderFactory {
         new LazyProviderClass(JAXB_PROVIDER_NAME);
     private static final LazyProviderClass JAXB_ELEMENT_PROVIDER_CLASS =
         new LazyProviderClass("org.apache.cxf.jaxrs.provider.JAXBElementTypedProvider");
-    private static final LazyProviderClass JSONB_PROVIDER_CLASS =
-        new LazyProviderClass("org.apache.openejb.server.cxf.rs.johnzon.TomEEJsonbProvider");
-    private static final LazyProviderClass JSONP_PROVIDER_CLASS =
-        new LazyProviderClass("org.apache.openejb.server.cxf.rs.johnzon.TomEEJsonpProvider");
     private static final LazyProviderClass MULTIPART_PROVIDER_CLASS =
         new LazyProviderClass("org.apache.cxf.jaxrs.provider.MultipartProvider");
 
@@ -218,8 +214,6 @@ public abstract class ProviderFactory {
                      new PrimitiveTextProvider<Object>(),
                      JAXB_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
                      JAXB_ELEMENT_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
-                     JSONP_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
-                     JSONB_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
                      MULTIPART_PROVIDER_CLASS.tryCreateInstance(factory.getBus()));
         Object prop = factory.getBus().getProperty("skip.default.json.provider.registration");
         if (!PropertyUtils.isTrue(prop)) {
@@ -859,9 +853,12 @@ public abstract class ProviderFactory {
         setProviders(true, false, userProviders.toArray());
     }
 
-    private static class MessageBodyReaderComparator
+    static class MessageBodyReaderComparator
         implements Comparator<ProviderInfo<MessageBodyReader<?>>> {
 
+        private final GenericArgumentComparator classComparator =
+                new GenericArgumentComparator(MessageBodyReader.class);
+
         public int compare(ProviderInfo<MessageBodyReader<?>> p1,
                            ProviderInfo<MessageBodyReader<?>> p2) {
             MessageBodyReader<?> e1 = p1.getProvider();
@@ -876,7 +873,10 @@ public abstract class ProviderFactory {
             if (result != 0) {
                 return result;
             }
-            result = compareClasses(e1, e2);
+
+            final Class<?> class1 = ClassHelper.getRealClass(e1);
+            final Class<?> class2 = ClassHelper.getRealClass(e2);
+            result = classComparator.compare(class1, class2);
             if (result != 0) {
                 return result;
             }
@@ -884,39 +884,54 @@ public abstract class ProviderFactory {
             if (result != 0) {
                 return result;
             }
-            return comparePriorityStatus(p1.getProvider().getClass(), p2.getProvider().getClass());
+
+            result = comparePriorityStatus(p1.getProvider().getClass(), p2.getProvider().getClass());
+            if (result != 0) {
+                return result;
+            }
+
+            return p1.getProvider().getClass().getName().compareTo(p2.getProvider().getClass().getName());
         }
     }
 
-    private static class MessageBodyWriterComparator
+    static class MessageBodyWriterComparator
         implements Comparator<ProviderInfo<MessageBodyWriter<?>>> {
 
+        private final GenericArgumentComparator classComparator =
+                new GenericArgumentComparator(MessageBodyWriter.class);
+
         public int compare(ProviderInfo<MessageBodyWriter<?>> p1,
                            ProviderInfo<MessageBodyWriter<?>> p2) {
             MessageBodyWriter<?> e1 = p1.getProvider();
             MessageBodyWriter<?> e2 = p2.getProvider();
 
+            final Class<?> class1 = ClassHelper.getRealClass(e1);
+            final Class<?> class2 = ClassHelper.getRealClass(e2);
+            int result = classComparator.compare(class1, class2);
+            if (result != 0) {
+                return result;
+            }
             List<MediaType> types1 =
                 JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(e1), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
             List<MediaType> types2 =
                 JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(e2), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
 
-            int result = JAXRSUtils.compareSortedMediaTypes(types1, types2, JAXRSUtils.MEDIA_TYPE_QS_PARAM);
+            result = JAXRSUtils.compareSortedMediaTypes(types1, types2, JAXRSUtils.MEDIA_TYPE_QS_PARAM);
             if (result != 0) {
                 return result;
             }
 
-            result = compareClasses(e1, e2);
+            result = compareCustomStatus(p1, p2);
             if (result != 0) {
                 return result;
             }
 
-            result = compareCustomStatus(p1, p2);
+            result = comparePriorityStatus(p1.getProvider().getClass(), p2.getProvider().getClass());
             if (result != 0) {
                 return result;
             }
 
-            return comparePriorityStatus(p1.getProvider().getClass(), p2.getProvider().getClass());
+            return p1.getProvider().getClass().getName().compareTo(p2.getProvider().getClass().getName());
         }
     }
 
@@ -1139,8 +1154,13 @@ public abstract class ProviderFactory {
         if (realClass1.isAssignableFrom(realClass2)) {
             // subclass should go first
             return 1;
+        } else if (realClass2.isAssignableFrom(realClass1)) {
+            // superclass should go last
+            return -1;
         }
-        return -1;
+
+        // there is no relation between the types returned by the providers
+        return 0;
     }
 
     private static Type[] getGenericInterfaces(Class<?> cls, Class<?> expectedClass) {
diff --git a/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/utils/GenericsUtils.java b/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/utils/GenericsUtils.java
new file mode 100644
index 0000000..ad7cca8
--- /dev/null
+++ b/tomee/apache-tomee/src/patch/java/org/apache/cxf/jaxrs/utils/GenericsUtils.java
@@ -0,0 +1,224 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.jaxrs.utils;
+
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public final class GenericsUtils {
+
+    private GenericsUtils() {
+        // no-op
+    }
+
+    /**
+     * Get the generic parameter for a specific interface we implement.  The generic types
+     * of other interfaces the specified class may implement will be ignored and not reported.
+     *
+     * If the interface has multiple generic parameters then multiple types will be returned.
+     * If the interface has no generic parameters, then a zero-length array is returned.
+     * If the class does not implement this interface, null will be returned.
+     *
+     * @param intrface The interface that has generic parameters
+     * @param clazz    The class implementing the interface and specifying the generic type
+     * @return the parameter types for this interface or null if the class does not implement the interface
+     */
+    public static Type[] getTypeArgumentsFor(final Class<?> intrface, final Class<?> clazz) {
+        if (!intrface.isAssignableFrom(clazz)) {
+            return null;
+        }
+
+        // Is this one of our immediate interfaces or super classes?
+        final Optional<Type[]> directTypes = genericTypes(clazz)
+                .filter(type -> type instanceof ParameterizedType)
+                .map(ParameterizedType.class::cast)
+                .filter(parameterizedType -> intrface.equals(parameterizedType.getRawType()))
+                .map(ParameterizedType::getActualTypeArguments)
+                .findFirst();
+
+        if (directTypes.isPresent()) {
+            return directTypes.get();
+        }
+
+        // Look at our parent and interface parents for the type
+        final Type[] types = declaredTypes(clazz)
+                .filter(Objects::nonNull)
+                .map(aClass -> getTypeArgumentsFor(intrface, aClass))
+                .filter(Objects::nonNull)
+                .findFirst()
+                .orElse(null);
+
+        if (types == null) {
+            // Our parent does not implement this interface.  We are
+            // assignable to this interface, so it must be coming from
+            // a place we aren't yet looking.  Feature gap.
+            return null;
+        }
+
+        // The types we got back may in fact have variables in them,
+        // in which case we need resolve them.
+        for (int i = 0; i < types.length; i++) {
+            types[i] = resolveTypeVariable(types[i], clazz);
+            types[i] = resolveParameterizedTypes(types[i], clazz);
+        }
+
+        return types;
+    }
+
+    private static Type resolveParameterizedTypes(final Type parameterized, final Class<?> clazz) {
+        // If this isn't actually a variable, return what they passed us
+        // as there is nothing to resolve
+        if (!(parameterized instanceof ParameterizedType)) {
+            return parameterized;
+        }
+
+        final ParameterizedType parameterizedType = (ParameterizedType) parameterized;
+
+        final Type[] types = parameterizedType.getActualTypeArguments();
+        boolean modified = false;
+        // The types we got back may in fact have variables in them,
+        // in which case we need resolve them.
+        for (int i = 0; i < types.length; i++) {
+            final Type original = types[i];
+            types[i] = resolveTypeVariable(types[i], clazz);
+            types[i] = resolveParameterizedTypes(types[i], clazz);
+            if (!original.equals(types[i])) {
+                modified = true;
+            }
+        }
+
+        //  We didn't have any work to do
+        if (!modified) {
+            return parameterized;
+        }
+
+        return new ResolvedParameterizedType(parameterizedType, types);
+    }
+
+    private static class ResolvedParameterizedType implements ParameterizedType {
+        private final ParameterizedType parameterizedType;
+        private final Type[] actualTypesResolved;
+
+        ResolvedParameterizedType(final ParameterizedType parameterizedType, final Type[] actualTypes) {
+            this.parameterizedType = parameterizedType;
+            this.actualTypesResolved = actualTypes;
+        }
+
+        @Override
+        public Type[] getActualTypeArguments() {
+            return actualTypesResolved;
+        }
+
+        @Override
+        public Type getRawType() {
+            return parameterizedType.getRawType();
+        }
+
+        @Override
+        public Type getOwnerType() {
+            return parameterizedType.getOwnerType();
+        }
+
+        @Override
+        public String getTypeName() {
+            return parameterizedType.getTypeName();
+        }
+    }
+
+    private static Type resolveTypeVariable(final Type variable, final Class<?> clazz) {
+        // If this isn't actually a variable, return what they passed us
+        // as there is nothing to resolve
+        if (!(variable instanceof TypeVariable)) {
+            return variable;
+        }
+
+        final TypeVariable<?> typeVariable = (TypeVariable<?>) variable;
+
+        // Where was this type variable declared?
+        final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
+
+        // At the moment we only support type variables on class definitions
+        // so if it isn't of type Class, return the unresolved variable
+        if (!(genericDeclaration instanceof Class)) {
+            return variable;
+        }
+        final Class<?> declaringClass = (Class<?>) genericDeclaration;
+
+        // Get the position of the variable in the generic signature
+        // where variable names are specified
+        final int typePosition = positionOf(variable, declaringClass.getTypeParameters());
+
+        // We cannot seem to find our type variable in the list of parameters?
+        // This shouldn't happen, but it did.  Return the unresolved variable
+        if (typePosition == -1) {
+            return variable;
+        }
+
+        // Get the actual type arguments passed from the place where the declaringClass
+        // was used by clazz in either an 'extends' or 'implements' context
+        final Type[] actualTypes = genericTypes(clazz)
+                .filter(type -> type instanceof ParameterizedType)
+                .map(ParameterizedType.class::cast)
+                .filter(parameterizedType -> declaringClass.equals(parameterizedType.getRawType()))
+                .map(ParameterizedType::getActualTypeArguments)
+                .findFirst().orElse(null);
+
+        // We cannot seem to find where the types are specified. We have a
+        // feature gap in our code. Return the unresolved variable
+        if (actualTypes == null) {
+            return variable;
+        }
+
+        // We found where the actual types were supplied, but somehow the
+        // array lengths don't line up? This shouldn't happen, but did.
+        // Return the unresolved variable
+        if (actualTypes.length != declaringClass.getTypeParameters().length) {
+            return variable;
+        }
+
+        final Type resolvedType = actualTypes[typePosition];
+
+        return resolvedType;
+    }
+
+    private static Stream<Type> genericTypes(Class<?> clazz) {
+        return Stream.concat(Stream.of(clazz.getGenericSuperclass()), Stream.of(clazz.getGenericInterfaces()));
+    }
+
+    private static Stream<Class<?>> declaredTypes(Class<?> clazz) {
+        return Stream.concat(Stream.of(clazz.getSuperclass()), Stream.of(clazz.getInterfaces()));
+    }
+
+    private static int positionOf(final Type variable, final TypeVariable<? extends Class<?>>[] typeParameters) {
+        for (int i = 0; i < typeParameters.length; i++) {
+            final TypeVariable<? extends Class<?>> typeParameter = typeParameters[i];
+            if (variable.equals(typeParameter)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+}