TSK-1763: refactored TaskanaConfigurationInitalizer
This commit is contained in:
parent
9d615c437a
commit
49183fea79
|
@ -8,6 +8,7 @@ import java.time.Duration;
|
|||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
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.internal.util.CheckedFunction;
|
||||
import pro.taskana.common.internal.util.Pair;
|
||||
import pro.taskana.common.internal.util.ReflectionUtil;
|
||||
|
||||
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_CLASSIFICATION_CATEGORIES_PROPERTY =
|
||||
"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() {
|
||||
throw new IllegalStateException("utility class");
|
||||
}
|
||||
|
||||
public 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());
|
||||
}
|
||||
public static void configureAnnotatedFields(Object instance, String separator, Properties props) {
|
||||
final List<Field> fields = ReflectionUtil.retrieveAllFields(instance.getClass());
|
||||
for (Field field : fields) {
|
||||
Optional.ofNullable(field.getAnnotation(TaskanaProperty.class))
|
||||
.ifPresent(
|
||||
taskanaProperty -> {
|
||||
Class<?> type = ReflectionUtil.wrap(field.getType());
|
||||
PropertyParser<?> propertyParser =
|
||||
Optional.ofNullable(PROPERTY_INITIALIZER_BY_CLASS.get(type))
|
||||
.orElseThrow(
|
||||
() -> new SystemException("Unknown configuration type " + type));
|
||||
propertyParser
|
||||
.initialize(props, separator, field, taskanaProperty)
|
||||
.ifPresent(value -> setFieldValue(instance, field, value));
|
||||
});
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Map<String, List<String>> configureClassificationCategoriesForType(
|
||||
|
@ -89,85 +103,6 @@ public class TaskanaConfigurationInitializer {
|
|||
.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) {
|
||||
return splitStringAndTrimElements(str, separator, UnaryOperator.identity());
|
||||
}
|
||||
|
@ -211,4 +146,98 @@ public class TaskanaConfigurationInitializer {
|
|||
"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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package pro.taskana.common.internal.util;
|
|||
import static pro.taskana.common.internal.util.CheckedFunction.wrap;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
@ -51,7 +49,7 @@ public class ObjectAttributeChangeDetector {
|
|||
}
|
||||
|
||||
List<JSONObject> changedAttributes =
|
||||
retrieveAllFields(objectClass).stream()
|
||||
ReflectionUtil.retrieveAllFields(objectClass).stream()
|
||||
.peek(field -> field.setAccessible(true))
|
||||
.filter(field -> !"customAttributes".equals(field.getName()))
|
||||
.map(wrap(field -> Triplet.of(field, field.get(oldObject), field.get(newObject))))
|
||||
|
@ -75,15 +73,6 @@ public class ObjectAttributeChangeDetector {
|
|||
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) {
|
||||
if (oldObject.equals(newObject)) {
|
||||
return "";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -63,21 +63,17 @@ public class TaskanaEngineConfiguration {
|
|||
// authorizations
|
||||
protected boolean securityEnabled;
|
||||
protected boolean useManagedTransactions;
|
||||
|
||||
@TaskanaProperty("taskana.custom.holidays")
|
||||
private List<CustomHoliday> customHolidays = new ArrayList<>();
|
||||
|
||||
// List of configured domain names
|
||||
@TaskanaProperty("taskana.domains")
|
||||
protected List<String> domains = new ArrayList<>();
|
||||
|
||||
// List of configured classification types
|
||||
@TaskanaProperty("taskana.classification.types")
|
||||
protected List<String> classificationTypes = new ArrayList<>();
|
||||
|
||||
@TaskanaProperty("taskana.classification.categories")
|
||||
protected Map<String, List<String>> classificationCategoriesByTypeMap = new HashMap<>();
|
||||
|
||||
@TaskanaProperty("taskana.custom.holidays")
|
||||
private List<CustomHoliday> customHolidays = new ArrayList<>();
|
||||
// Properties for the monitor
|
||||
@TaskanaProperty("taskana.history.deletion.on.task.deletion.enabled")
|
||||
private boolean deleteHistoryOnTaskDeletionEnabled;
|
||||
|
@ -111,7 +107,7 @@ public class TaskanaEngineConfiguration {
|
|||
|
||||
@TaskanaProperty("taskana.validation.allowTimestampServiceLevelMismatch")
|
||||
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")
|
||||
private boolean addAdditionalUserInfo = false;
|
||||
|
||||
|
|
Loading…
Reference in New Issue