This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/CXF-6880 in repository https://gitbox.apache.org/repos/asf/cxf.git
commit bb84bd1152a37810e315ca4921ceffe2ec1a48f0 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Mon Oct 12 11:16:31 2026 +0100 CXF-6880 - Resolve generic property types in search condition parsers --- .../ext/search/AbstractSearchConditionParser.java | 29 ++++++++-- .../apache/cxf/jaxrs/ext/search/Beanspector.java | 27 +++++++++- .../cxf/jaxrs/ext/search/fiql/FiqlParserTest.java | 58 ++++++++++++++++++++ .../jaxrs/ext/search/odata/ODataParserTest.java | 61 +++++++++++++++++++++- 4 files changed, 168 insertions(+), 7 deletions(-) diff --git a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java index defd06bb8a6..a651f7841b2 100644 --- a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java +++ b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java @@ -21,6 +21,7 @@ package org.apache.cxf.jaxrs.ext.search; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.lang.reflect.Type; import java.sql.Time; import java.sql.Timestamp; import java.text.DateFormat; @@ -183,10 +184,13 @@ public abstract class AbstractSearchConditionParser<T> implements SearchConditio try { String nextPart = getMethodNameSuffix(names[1]); Method getterM = actualType.getMethod("get" + nextPart, new Class[]{}); - Class<?> returnType = getterM.getReturnType(); + // The getter may be declared by a generic superclass, e.g. 'PK getId()', + // in which case its type has to be resolved against the actual class + Type genericReturnType = Beanspector.resolveType(actualType, getterM.getGenericReturnType()); + Class<?> returnType = Beanspector.getRawType(genericReturnType, getterM.getReturnType()); boolean returnCollection = InjectionUtils.isSupportedCollectionOrArray(returnType); Class<?> actualReturnType = !returnCollection ? returnType - : InjectionUtils.getActualType(getterM.getGenericReturnType()); + : InjectionUtils.getActualType(genericReturnType); boolean isPrimitive = !returnCollection && InjectionUtils.isPrimitive(returnType) || returnType.isEnum(); @@ -229,7 +233,7 @@ public abstract class AbstractSearchConditionParser<T> implements SearchConditio } else { nextObject = actualReturnType.getDeclaredConstructor().newInstance(); } - Method setterM = actualType.getMethod("set" + nextPart, new Class[]{returnType}); + Method setterM = getSetterMethod(actualType, "set" + nextPart, returnType); final Object valueObjectValue; if (lastTry || !returnCollection) { valueObjectValue = nextObject; @@ -245,7 +249,7 @@ public abstract class AbstractSearchConditionParser<T> implements SearchConditio } lastCastedValue = valueObject; - TypeInfo nextTypeInfo = new TypeInfo(valueObjectValue.getClass(), getterM.getGenericReturnType()); + TypeInfo nextTypeInfo = new TypeInfo(valueObjectValue.getClass(), genericReturnType); Object response = parseType(originalPropName, nextObject, lastCastedValue, @@ -262,6 +266,23 @@ public abstract class AbstractSearchConditionParser<T> implements SearchConditio } } + private static Method getSetterMethod(Class<?> cls, String name, Class<?> paramType) + throws NoSuchMethodException { + try { + return cls.getMethod(name, new Class[]{paramType}); + } catch (NoSuchMethodException ex) { + // The setter may be declared by a generic superclass, e.g. 'setId(PK id)', + // in which case its erased parameter type is a supertype of the resolved one + for (Method m : cls.getMethods()) { + if (m.getName().equals(name) && m.getParameterCount() == 1 + && m.getParameterTypes()[0].isAssignableFrom(paramType)) { + return m; + } + } + throw ex; + } + } + private boolean paramConverterAvailable(final Class<?> pClass) { Message m = JAXRSUtils.getCurrentMessage(); ServerProviderFactory pf = m == null ? null : ServerProviderFactory.getInstance(m); diff --git a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java index 42e949355ac..253f6c080f6 100644 --- a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java +++ b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java @@ -22,6 +22,7 @@ import java.beans.IntrospectionException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -32,6 +33,7 @@ import java.util.Map; import java.util.Set; import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckInfo; +import org.apache.cxf.jaxrs.utils.InjectionUtils; /** * Bean introspection utility. @@ -140,8 +142,29 @@ public class Beanspector<T> { setters.keySet(), getters.keySet()); throw new IntrospectionException(msg); } - return new TypeInfo(m.getReturnType(), m.getGenericReturnType(), - primitiveToWrapper(m.getReturnType())); + Type genericType = resolveType(tclass, m.getGenericReturnType()); + Class<?> cls = getRawType(genericType, m.getReturnType()); + return new TypeInfo(cls, genericType, primitiveToWrapper(cls)); + } + + /** + * Resolves a type variable (for example, the type of a property declared in a generic + * superclass such as {@code AbstractEntity<PK extends Serializable>}) against the given + * concrete class. Any other type is returned unchanged. + */ + static Type resolveType(Class<?> concreteClass, Type type) { + if (type instanceof TypeVariable) { + Type resolved = InjectionUtils.getSuperType(concreteClass, (TypeVariable<?>)type); + if (resolved != null) { + return resolved; + } + } + return type; + } + + static Class<?> getRawType(Type type, Class<?> defaultType) { + Class<?> cls = InjectionUtils.getRawType(type); + return cls != null ? cls : defaultType; } public Beanspector<T> swap(T newobject) throws Exception { diff --git a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserTest.java b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserTest.java index db406954c95..5c4b6e16915 100644 --- a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserTest.java +++ b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserTest.java @@ -18,6 +18,7 @@ */ package org.apache.cxf.jaxrs.ext.search.fiql; +import java.io.Serializable; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -448,6 +449,22 @@ public class FiqlParserTest { } } + @Test + public void testGenericSuperclassProperty() throws SearchParseException { + SearchCondition<CallHeader> filter = new FiqlParser<>(CallHeader.class).parse("id==1"); + assertEquals(Long.valueOf(1L), filter.getStatement().getValue()); + assertTrue(filter.isMet(new CallHeader().withId(1L))); + assertFalse(filter.isMet(new CallHeader().withId(2L))); + } + + @Test + public void testNestedGenericSuperclassProperty() throws SearchParseException { + SearchCondition<CallHeader> filter = new FiqlParser<>(CallHeader.class).parse("call.id==1"); + assertEquals(Long.valueOf(1L), ((Call)filter.getStatement().getValue()).getId()); + assertTrue(filter.isMet(new CallHeader().withCall(new Call().withId(1L)))); + assertFalse(filter.isMet(new CallHeader().withCall(new Call().withId(2L)))); + } + @Ignore public static class Condition { private String name; @@ -616,6 +633,47 @@ public class FiqlParserTest { } + public abstract static class AbstractEntity<PK extends Serializable> { + private PK id; + + public PK getId() { + return id; + } + + public void setId(PK id) { + this.id = id; + } + } + + public static class Call extends AbstractEntity<Long> { + Call withId(Long newId) { + setId(newId); + return this; + } + } + + public static class CallHeader extends AbstractEntity<Long> { + private Call call; + + public Call getCall() { + return call; + } + + public void setCall(Call call) { + this.call = call; + } + + CallHeader withId(Long newId) { + setId(newId); + return this; + } + + CallHeader withCall(Call newCall) { + setCall(newCall); + return this; + } + } + public static class Job { private List<Task> tasks; public List<Task> getTasks() { diff --git a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/ODataParserTest.java b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/ODataParserTest.java index a2bb3ff666d..c4eba5dbd60 100644 --- a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/ODataParserTest.java +++ b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/ODataParserTest.java @@ -18,6 +18,7 @@ */ package org.apache.cxf.jaxrs.ext.search.odata; +import java.io.Serializable; import java.util.Collections; import org.apache.cxf.jaxrs.ext.search.SearchCondition; @@ -26,6 +27,7 @@ import org.apache.cxf.jaxrs.ext.search.SearchParseException; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -117,6 +119,47 @@ public class ODataParserTest { } } + public abstract static class AbstractEntity<PK extends Serializable> { + private PK id; + + public PK getId() { + return id; + } + + public void setId(PK id) { + this.id = id; + } + } + + public static class Call extends AbstractEntity<Long> { + Call withId(Long newId) { + setId(newId); + return this; + } + } + + public static class CallHeader extends AbstractEntity<Long> { + private Call call; + + public Call getCall() { + return call; + } + + public void setCall(Call call) { + this.call = call; + } + + CallHeader withId(Long newId) { + setId(newId); + return this; + } + + CallHeader withCall(Call newCall) { + setCall(newCall); + return this; + } + } + @Before public void setUp() { parser = new ODataParser<>(Person.class, Collections.<String, String>emptyMap(), @@ -196,4 +239,20 @@ public class ODataParserTest { assertTrue(filter.isMet(new Person("Tom", "Bombadil").withSsn(553232222L))); assertFalse(filter.isMet(new Person("Tom", "Bombadil").withHourlyRate(748232221L))); } -} \ No newline at end of file + + @Test + public void testFilterByGenericSuperclassProperty() throws SearchParseException { + SearchCondition< CallHeader > filter = new ODataParser<>(CallHeader.class).parse("id eq 1"); + assertEquals(Long.valueOf(1L), filter.getStatement().getValue()); + assertTrue(filter.isMet(new CallHeader().withId(1L))); + assertFalse(filter.isMet(new CallHeader().withId(2L))); + } + + @Test + public void testFilterByNestedGenericSuperclassProperty() throws SearchParseException { + SearchCondition< CallHeader > filter = new ODataParser<>(CallHeader.class).parse("call.id eq 1"); + assertEquals(Long.valueOf(1L), ((Call)filter.getStatement().getValue()).getId()); + assertTrue(filter.isMet(new CallHeader().withCall(new Call().withId(1L)))); + assertFalse(filter.isMet(new CallHeader().withCall(new Call().withId(2L)))); + } +}
