TSK-1763: refactored TaskanaConfigurationInitalizer

This commit is contained in:
Mustapha Zorgati 2021-10-29 16:24:33 +02:00
parent 9d615c437a
commit 49183fea79
5 changed files with 219 additions and 112 deletions

View File

@ -8,6 +8,7 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -28,6 +29,7 @@ import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.WrongCustomHolidayFormatException; import pro.taskana.common.api.exceptions.WrongCustomHolidayFormatException;
import pro.taskana.common.internal.util.CheckedFunction; import pro.taskana.common.internal.util.CheckedFunction;
import pro.taskana.common.internal.util.Pair; import pro.taskana.common.internal.util.Pair;
import pro.taskana.common.internal.util.ReflectionUtil;
public class TaskanaConfigurationInitializer { public class TaskanaConfigurationInitializer {
@ -36,26 +38,38 @@ public class TaskanaConfigurationInitializer {
private static final String TASKANA_CUSTOM_HOLIDAY_DAY_MONTH_SEPARATOR = "."; private static final String TASKANA_CUSTOM_HOLIDAY_DAY_MONTH_SEPARATOR = ".";
private static final String TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY = private static final String TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY =
"taskana.classification.categories"; "taskana.classification.categories";
private static final Map<Class<?>, PropertyParser<?>> PROPERTY_INITIALIZER_BY_CLASS =
new HashMap<>();
static {
PROPERTY_INITIALIZER_BY_CLASS.put(Integer.class, new IntegerPropertyParser());
PROPERTY_INITIALIZER_BY_CLASS.put(Boolean.class, new BooleanPropertyParser());
PROPERTY_INITIALIZER_BY_CLASS.put(String.class, new StringPropertyParser());
PROPERTY_INITIALIZER_BY_CLASS.put(Duration.class, new DurationPropertyParser());
PROPERTY_INITIALIZER_BY_CLASS.put(Instant.class, new InstantPropertyParser());
PROPERTY_INITIALIZER_BY_CLASS.put(List.class, new ListPropertyParser());
}
private TaskanaConfigurationInitializer() { private TaskanaConfigurationInitializer() {
throw new IllegalStateException("utility class"); throw new IllegalStateException("utility class");
} }
public static <T> Optional<T> parseProperty( public static void configureAnnotatedFields(Object instance, String separator, Properties props) {
Properties props, String key, CheckedFunction<String, T, Exception> function) { final List<Field> fields = ReflectionUtil.retrieveAllFields(instance.getClass());
String property = props.getProperty(key, ""); for (Field field : fields) {
if (!property.isEmpty()) { Optional.ofNullable(field.getAnnotation(TaskanaProperty.class))
try { .ifPresent(
return Optional.ofNullable(function.apply(property)); taskanaProperty -> {
} catch (Throwable t) { Class<?> type = ReflectionUtil.wrap(field.getType());
LOGGER.warn( PropertyParser<?> propertyParser =
"Could not parse property {} ({}). Using default. Exception: {}", Optional.ofNullable(PROPERTY_INITIALIZER_BY_CLASS.get(type))
key, .orElseThrow(
property, () -> new SystemException("Unknown configuration type " + type));
t.getMessage()); propertyParser
} .initialize(props, separator, field, taskanaProperty)
.ifPresent(value -> setFieldValue(instance, field, value));
});
} }
return Optional.empty();
} }
public static Map<String, List<String>> configureClassificationCategoriesForType( public static Map<String, List<String>> configureClassificationCategoriesForType(
@ -89,85 +103,6 @@ public class TaskanaConfigurationInitializer {
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
} }
public static List<Field> getAllFields(List<Field> fields, Class<?> type) {
fields.addAll(Arrays.asList(type.getDeclaredFields()));
if (type.getSuperclass() != null) {
getAllFields(fields, type.getSuperclass());
}
return fields;
}
public static void configureAnnotatedFields(Object instance, String separator, Properties props) {
final List<Field> fields = getAllFields(new ArrayList<>(), instance.getClass());
for (Field field : fields) {
Optional.ofNullable(field.getAnnotation(TaskanaProperty.class))
.ifPresent(
taskanaProperty -> {
final String fieldPropertyName = taskanaProperty.value();
final Class<?> type = field.getType();
String name = type.getSimpleName();
switch (name) {
case "int":
parseProperty(props, fieldPropertyName, Integer::parseInt)
.ifPresent(value -> setFieldValue(instance, field, value));
break;
case "boolean":
parseProperty(props, fieldPropertyName, Boolean::parseBoolean)
.ifPresent(value -> setFieldValue(instance, field, value));
break;
case "String":
parseProperty(props, fieldPropertyName, String::new)
.ifPresent(value -> setFieldValue(instance, field, value));
break;
case "Duration":
parseProperty(props, fieldPropertyName, Duration::parse)
.ifPresent(value -> setFieldValue(instance, field, value));
break;
case "Instant":
parseProperty(props, fieldPropertyName, Instant::parse)
.ifPresent(value -> setFieldValue(instance, field, value));
break;
case "List":
final String typeName =
((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0].getTypeName();
if (typeName.equals("pro.taskana.common.api.CustomHoliday")) {
CheckedFunction<String, List<CustomHoliday>, Exception> parseFunction2 =
s ->
splitStringAndTrimElements(s, separator).stream()
.map(
str -> {
try {
return createCustomHolidayFromPropsEntry(str);
} catch (WrongCustomHolidayFormatException e) {
LOGGER.warn(e.getMessage());
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
parseProperty(props, fieldPropertyName, parseFunction2)
.ifPresent(value -> setFieldValue(instance, field, value));
} else {
CheckedFunction<String, List<String>, Exception> parseListFunction =
p -> splitStringAndTrimElements(p, ",", String::toUpperCase);
parseProperty(props, fieldPropertyName, parseListFunction)
.ifPresent(value -> setFieldValue(instance, field, value));
}
break;
case "Map":
// TODO
break;
default:
throw new SystemException("Unknown configuration type " + name);
}
});
}
}
static List<String> splitStringAndTrimElements(String str, String separator) { static List<String> splitStringAndTrimElements(String str, String separator) {
return splitStringAndTrimElements(str, separator, UnaryOperator.identity()); return splitStringAndTrimElements(str, separator, UnaryOperator.identity());
} }
@ -211,4 +146,98 @@ public class TaskanaConfigurationInitializer {
"Property value " + value + " is invalid for field " + field.getName(), e); "Property value " + value + " is invalid for field " + field.getName(), e);
} }
} }
private static <T> Optional<T> parseProperty(
Properties props, String key, CheckedFunction<String, T, Exception> function) {
String property = props.getProperty(key, "");
if (!property.isEmpty()) {
try {
return Optional.ofNullable(function.apply(property));
} catch (Throwable t) {
LOGGER.warn(
"Could not parse property {} ({}). Using default. Exception: {}",
key,
property,
t.getMessage());
}
}
return Optional.empty();
}
interface PropertyParser<T> {
Optional<T> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty);
}
static class ListPropertyParser implements PropertyParser<List<?>> {
@Override
public Optional<List<?>> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty) {
final String typeName =
((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].getTypeName();
if (typeName.equals("pro.taskana.common.api.CustomHoliday")) {
CheckedFunction<String, List<?>, Exception> parseFunction2 =
s ->
splitStringAndTrimElements(s, separator).stream()
.map(
str -> {
try {
return createCustomHolidayFromPropsEntry(str);
} catch (WrongCustomHolidayFormatException e) {
LOGGER.warn(e.getMessage());
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
return parseProperty(properties, taskanaProperty.value(), parseFunction2);
} else {
CheckedFunction<String, List<?>, Exception> parseListFunction =
p -> splitStringAndTrimElements(p, ",", String::toUpperCase);
return parseProperty(properties, taskanaProperty.value(), parseListFunction);
}
}
}
static class InstantPropertyParser implements PropertyParser<Instant> {
@Override
public Optional<Instant> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty) {
return parseProperty(properties, taskanaProperty.value(), Instant::parse);
}
}
static class DurationPropertyParser implements PropertyParser<Duration> {
@Override
public Optional<Duration> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty) {
return parseProperty(properties, taskanaProperty.value(), Duration::parse);
}
}
static class StringPropertyParser implements PropertyParser<String> {
@Override
public Optional<String> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty) {
return parseProperty(properties, taskanaProperty.value(), String::new);
}
}
static class IntegerPropertyParser implements PropertyParser<Integer> {
@Override
public Optional<Integer> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty) {
return parseProperty(properties, taskanaProperty.value(), Integer::parseInt);
}
}
static class BooleanPropertyParser implements PropertyParser<Boolean> {
@Override
public Optional<Boolean> initialize(
Properties properties, String separator, Field field, TaskanaProperty taskanaProperty) {
return parseProperty(properties, taskanaProperty.value(), Boolean::parseBoolean);
}
}
} }

View File

@ -3,8 +3,6 @@ package pro.taskana.common.internal.util;
import static pro.taskana.common.internal.util.CheckedFunction.wrap; import static pro.taskana.common.internal.util.CheckedFunction.wrap;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -51,7 +49,7 @@ public class ObjectAttributeChangeDetector {
} }
List<JSONObject> changedAttributes = List<JSONObject> changedAttributes =
retrieveAllFields(objectClass).stream() ReflectionUtil.retrieveAllFields(objectClass).stream()
.peek(field -> field.setAccessible(true)) .peek(field -> field.setAccessible(true))
.filter(field -> !"customAttributes".equals(field.getName())) .filter(field -> !"customAttributes".equals(field.getName()))
.map(wrap(field -> Triplet.of(field, field.get(oldObject), field.get(newObject)))) .map(wrap(field -> Triplet.of(field, field.get(oldObject), field.get(newObject))))
@ -75,15 +73,6 @@ public class ObjectAttributeChangeDetector {
return changedAttribute; return changedAttribute;
} }
private static List<Field> retrieveAllFields(Class<?> currentClass) {
List<Field> fields = new ArrayList<>();
while (currentClass.getSuperclass() != null) {
fields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}
return fields;
}
private static <T> String compareLists(T oldObject, T newObject) { private static <T> String compareLists(T oldObject, T newObject) {
if (oldObject.equals(newObject)) { if (oldObject.equals(newObject)) {
return ""; return "";

View File

@ -0,0 +1,45 @@
package pro.taskana.common.internal.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ReflectionUtil {
private static final Map<Class<?>, Class<?>> PRIMITIVES_TO_WRAPPERS = new HashMap<>();
static {
PRIMITIVES_TO_WRAPPERS.put(boolean.class, Boolean.class);
PRIMITIVES_TO_WRAPPERS.put(byte.class, Byte.class);
PRIMITIVES_TO_WRAPPERS.put(char.class, Character.class);
PRIMITIVES_TO_WRAPPERS.put(double.class, Double.class);
PRIMITIVES_TO_WRAPPERS.put(float.class, Float.class);
PRIMITIVES_TO_WRAPPERS.put(int.class, Integer.class);
PRIMITIVES_TO_WRAPPERS.put(long.class, Long.class);
PRIMITIVES_TO_WRAPPERS.put(short.class, Short.class);
PRIMITIVES_TO_WRAPPERS.put(void.class, Void.class);
}
private ReflectionUtil() {
throw new IllegalStateException("utility class");
}
public static List<Field> retrieveAllFields(Class<?> currentClass) {
List<Field> fields = new ArrayList<>();
while (currentClass.getSuperclass() != null) {
fields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}
return fields.stream().filter(f -> !f.isSynthetic()).collect(Collectors.toList());
}
// safe because both Long.class and long.class are of type Class<Long>
@SuppressWarnings("unchecked")
public static <T> Class<T> wrap(Class<T> c) {
return c.isPrimitive() ? (Class<T>) PRIMITIVES_TO_WRAPPERS.get(c) : c;
}
}

View File

@ -0,0 +1,48 @@
package pro.taskana.common.internal.util;
import static org.assertj.core.api.Assertions.assertThat;
import java.lang.reflect.Field;
import java.util.List;
import org.junit.jupiter.api.Test;
public class ReflectionUtilTest {
@Test
void should_RetrieveAllFieldsFromClassAndSuperClass() {
List<Field> fields = ReflectionUtil.retrieveAllFields(SubSubTestClass.class);
assertThat(fields)
.extracting(Field::getName)
.containsExactlyInAnyOrder("fieldA", "fieldB", "fieldC");
}
@Test
void should_WrapPrimitiveClassToItsWrapperClass() {
Class<Integer> wrap = ReflectionUtil.wrap(int.class);
assertThat(wrap).isEqualTo(Integer.class);
}
@Test
void should_NotWrapNonPrimitiveClass() {
Class<TestClass> wrap = ReflectionUtil.wrap(TestClass.class);
assertThat(wrap).isEqualTo(TestClass.class);
}
static class TestClass {
@SuppressWarnings("unused")
String fieldA;
}
static class SubTestClass extends TestClass {
@SuppressWarnings("unused")
String fieldB;
}
static class SubSubTestClass extends SubTestClass {
@SuppressWarnings("unused")
String fieldC;
}
}

View File

@ -63,21 +63,17 @@ public class TaskanaEngineConfiguration {
// authorizations // authorizations
protected boolean securityEnabled; protected boolean securityEnabled;
protected boolean useManagedTransactions; protected boolean useManagedTransactions;
@TaskanaProperty("taskana.custom.holidays")
private List<CustomHoliday> customHolidays = new ArrayList<>();
// List of configured domain names // List of configured domain names
@TaskanaProperty("taskana.domains") @TaskanaProperty("taskana.domains")
protected List<String> domains = new ArrayList<>(); protected List<String> domains = new ArrayList<>();
// List of configured classification types // List of configured classification types
@TaskanaProperty("taskana.classification.types") @TaskanaProperty("taskana.classification.types")
protected List<String> classificationTypes = new ArrayList<>(); protected List<String> classificationTypes = new ArrayList<>();
@TaskanaProperty("taskana.classification.categories")
protected Map<String, List<String>> classificationCategoriesByTypeMap = new HashMap<>(); protected Map<String, List<String>> classificationCategoriesByTypeMap = new HashMap<>();
@TaskanaProperty("taskana.custom.holidays")
private List<CustomHoliday> customHolidays = new ArrayList<>();
// Properties for the monitor // Properties for the monitor
@TaskanaProperty("taskana.history.deletion.on.task.deletion.enabled") @TaskanaProperty("taskana.history.deletion.on.task.deletion.enabled")
private boolean deleteHistoryOnTaskDeletionEnabled; private boolean deleteHistoryOnTaskDeletionEnabled;
@ -111,7 +107,7 @@ public class TaskanaEngineConfiguration {
@TaskanaProperty("taskana.validation.allowTimestampServiceLevelMismatch") @TaskanaProperty("taskana.validation.allowTimestampServiceLevelMismatch")
private boolean validationAllowTimestampServiceLevelMismatch = false; private boolean validationAllowTimestampServiceLevelMismatch = false;
//Property to enable/disable the addition of user full/long name through joins // Property to enable/disable the addition of user full/long name through joins
@TaskanaProperty("taskana.addAdditionalUserInfo") @TaskanaProperty("taskana.addAdditionalUserInfo")
private boolean addAdditionalUserInfo = false; private boolean addAdditionalUserInfo = false;