Skip to content

Commit

Permalink
[KOGITO-5823] Supporting uppercase properties
Browse files Browse the repository at this point in the history
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
  • Loading branch information
fjtirado committed Sep 1, 2023
1 parent 0fa7dbf commit 631a08a
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 114 deletions.
8 changes: 2 additions & 6 deletions api/kogito-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
Expand All @@ -74,11 +74,7 @@
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down
3 changes: 1 addition & 2 deletions api/kogito-api/src/main/java/org/kie/kogito/MapInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ public interface MapInput {
* in the class
*/
default MapInput fromMap(Map<String, Object> params) {
Models.fromMap(this, params);
return this;
return Models.fromMap(this, params);
}

}
134 changes: 28 additions & 106 deletions api/kogito-api/src/main/java/org/kie/kogito/Models.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> toMap(Object m) {
try {
Map<String, Object> map = new HashMap<>();
BeanInfo beanInfo = Introspector.getBeanInfo(m.getClass());
Map<String, PropertyDescriptor> descriptors = descriptorMap(beanInfo);

for (Map.Entry<String, PropertyDescriptor> 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<Map<String, Object>>() {
});
}

public static <T> T fromMap(T m, String id, Map<String, Object> map) {
Expand All @@ -70,86 +46,32 @@ public static <T> T fromMap(T m, String id, Map<String, Object> map) {
}

public static <T> T fromMap(T m, Map<String, Object> map) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(m.getClass());
Map<String, PropertyDescriptor> descriptors = descriptorMap(beanInfo);

for (Map.Entry<String, PropertyDescriptor> 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> T fromMap(Class<T> cls, Map<String, Object> map) {
try {
Constructor<T> 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 <I, O> 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<String, PropertyDescriptor> descriptorMap(BeanInfo beanInfo) {
return Arrays.stream(beanInfo.getPropertyDescriptors())
.filter(pd -> !pd.getName().equals(CLASS_PROP))
.collect(Collectors.toMap(
PropertyDescriptor::getName,
Function.identity()));
}

}

0 comments on commit 631a08a

Please sign in to comment.