Skip to content

Commit

Permalink
[KOGITO-5823] Supporting uppercase properties (#3210)
Browse files Browse the repository at this point in the history
* [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

* [KOGITO-5832] Without using mapper

* [KOGITO-5823] Fixing SWF

It is better for it to not depend on Models
  • Loading branch information
fjtirado authored Sep 5, 2023
1 parent 3a67eb1 commit 5297d67
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 109 deletions.
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);
}

}
137 changes: 30 additions & 107 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,141 +15,64 @@
*/
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.LinkedHashMap;
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;

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 Models() {
}

@SuppressWarnings("squid:S3011")
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;
Map<String, Object> map = new LinkedHashMap<>();
for (Field field : m.getClass().getDeclaredFields()) {
JsonProperty jsonAnnotation = field.getAnnotation(JsonProperty.class);
if (jsonAnnotation != null) {
String name = jsonAnnotation.value();
field.setAccessible(true);
try {
map.put(name, field.get(m));
} catch (ReflectiveOperationException e) {
throw new ReflectiveModelAccessException(e);
}
k = unprefixVar(k);
map.put(k, e.getValue().getReadMethod().invoke(m));
}
return map;
} catch (IntrospectionException | ReflectiveOperationException e) {
throw new ReflectiveModelAccessException(e);
}
return map;
}

public static <T> T fromMap(T m, String id, Map<String, Object> map) {
setId(m, id);
return fromMap(m, map);
}

@SuppressWarnings("squid:S3011")
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);
}
}

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ public void fromMap(String id, Map<String, Object> params) {
update(id, mutableMap(params));
}

@Override
public MapInput fromMap(Map<String, Object> params) {
update(params);
return this;
}

@Override
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
Expand Down

0 comments on commit 5297d67

Please sign in to comment.