From 631a08aff362e645c08deb7067c5a7db48aeaa1e Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Thu, 31 Aug 2023 19:23:25 +0200 Subject: [PATCH] [KOGITO-5823] Supporting uppercase properties BeanInstrospector does not work with uppercase. Lets take benefit that the generated code is annotated with JsonProperty and use jackson for serialization/deserialization of Map into Model and Model into Map --- api/kogito-api/pom.xml | 8 +- .../main/java/org/kie/kogito/MapInput.java | 3 +- .../src/main/java/org/kie/kogito/Models.java | 134 ++++-------------- 3 files changed, 31 insertions(+), 114 deletions(-) diff --git a/api/kogito-api/pom.xml b/api/kogito-api/pom.xml index 85c613d88b0..aebd7841acf 100755 --- a/api/kogito-api/pom.xml +++ b/api/kogito-api/pom.xml @@ -50,7 +50,7 @@ com.fasterxml.jackson.core - jackson-annotations + jackson-databind @@ -74,11 +74,7 @@ junit-jupiter-engine test - - com.fasterxml.jackson.core - jackson-databind - test - + org.assertj assertj-core diff --git a/api/kogito-api/src/main/java/org/kie/kogito/MapInput.java b/api/kogito-api/src/main/java/org/kie/kogito/MapInput.java index 267eb4362a8..b0c143266d2 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/MapInput.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/MapInput.java @@ -29,8 +29,7 @@ public interface MapInput { * in the class */ default MapInput fromMap(Map params) { - Models.fromMap(this, params); - return this; + return Models.fromMap(this, params); } } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/Models.java b/api/kogito-api/src/main/java/org/kie/kogito/Models.java index 2143a48f9d9..a621a526638 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/Models.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/Models.java @@ -15,53 +15,29 @@ */ package org.kie.kogito; -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.HashMap; +import java.lang.reflect.Field; import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; public class Models { - private static final Logger LOGGER = LoggerFactory.getLogger(Models.class); - private static final String CLASS_PROP = "class"; - private static final String ID_PROP = "id"; - /** - * this prefix is only used when a variable name - * clashes with a predefined Java keyword (e.g. `static`) - */ - private static final String VAR_PREFIX = "v$"; + private static final ObjectMapper mapper = JsonMapper.builder() + .disable(MapperFeature.AUTO_DETECT_CREATORS, MapperFeature.AUTO_DETECT_FIELDS, MapperFeature.AUTO_DETECT_GETTERS, MapperFeature.AUTO_DETECT_IS_GETTERS) + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .build(); private Models() { } public static Map toMap(Object m) { - try { - Map map = new HashMap<>(); - BeanInfo beanInfo = Introspector.getBeanInfo(m.getClass()); - Map descriptors = descriptorMap(beanInfo); - - for (Map.Entry e : descriptors.entrySet()) { - String k = e.getKey(); - if (isIdentifier(k)) { - LOGGER.trace("Models#toMap: Skipping `id` property for class `{}`", m.getClass().getCanonicalName()); - continue; - } - k = unprefixVar(k); - map.put(k, e.getValue().getReadMethod().invoke(m)); - } - return map; - } catch (IntrospectionException | ReflectiveOperationException e) { - throw new ReflectiveModelAccessException(e); - } + return mapper.convertValue(m, new TypeReference>() { + }); } public static T fromMap(T m, String id, Map map) { @@ -70,86 +46,32 @@ public static T fromMap(T m, String id, Map map) { } public static T fromMap(T m, Map map) { - try { - BeanInfo beanInfo = Introspector.getBeanInfo(m.getClass()); - Map descriptors = descriptorMap(beanInfo); - - for (Map.Entry e : descriptors.entrySet()) { - String k = e.getKey(); - k = unprefixVar(k); - if (map.containsKey(k)) { - e.getValue().getWriteMethod().invoke(m, map.get(k)); + for (Field field : m.getClass().getDeclaredFields()) { + JsonProperty jsonAnnotation = field.getAnnotation(JsonProperty.class); + if (jsonAnnotation != null) { + String name = jsonAnnotation.value(); + if (map.containsKey(name)) { + field.setAccessible(true); + try { + field.set(m, map.get(name)); + } catch (ReflectiveOperationException e) { + throw new ReflectiveModelAccessException(e); + } } } - return m; - } catch (IntrospectionException | ReflectiveOperationException e) { - throw new ReflectiveModelAccessException(e); } + return m; } public static T fromMap(Class cls, Map map) { - try { - Constructor constructor = cls.getConstructor(); - T t = constructor.newInstance(); - fromMap(t, map); - return t; - } catch (NoSuchMethodException e) { - throw new ReflectiveModelAccessException( - String.format("Class `%s` must declare an empty constructor.", cls.getCanonicalName()), e); - } catch (ReflectiveOperationException e) { - throw new ReflectiveModelAccessException(e); - } + return mapper.convertValue(map, cls); } public static void setId(Object m, String id) { try { - BeanInfo beanInfo = Introspector.getBeanInfo(m.getClass()); - for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { - if (isIdentifier(pd.getName())) { - pd.getWriteMethod().invoke(m, id); - return; - } - } - // no Id found, throw error - throw new ReflectiveModelAccessException( - String.format( - "No `id` property found for class `%s`. Have you defined getters and setters?", - m.getClass().getCanonicalName())); - } catch (IntrospectionException | ReflectiveOperationException e) { + m.getClass().getMethod("setId", String.class).invoke(m, id); + } catch (ReflectiveOperationException e) { throw new ReflectiveModelAccessException(e); } - } - - public static O convert(I in, O out) { - fromMap(out, toMap(in)); - return out; - } - - /** - * When a process variable name clashes with a predefined - * Java keyword (e.g. `static`), we are prefixing the field - * with `v$` (e.g. `v$static`). - * - * @return the unprefixed variable name - */ - private static String unprefixVar(String k) { - if (k.startsWith(VAR_PREFIX)) { - k = k.substring(VAR_PREFIX.length()); - } - return k; - } - - private static boolean isIdentifier(String k) { - return k.equals(ID_PROP); - } - - private static Map descriptorMap(BeanInfo beanInfo) { - return Arrays.stream(beanInfo.getPropertyDescriptors()) - .filter(pd -> !pd.getName().equals(CLASS_PROP)) - .collect(Collectors.toMap( - PropertyDescriptor::getName, - Function.identity())); - } - }