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

Reply via email to