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;
+ }
+
+}