TSK-1745: use annotations to link config files with TaskanaEngineConfiguration
This commit is contained in:
parent
b5ee514628
commit
8b5d372222
|
@ -0,0 +1,210 @@
|
||||||
|
package pro.taskana.common.internal.configuration;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import pro.taskana.common.api.CustomHoliday;
|
||||||
|
import pro.taskana.common.api.TaskanaRole;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class TaskanaConfigurationInitializer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER =
|
||||||
|
LoggerFactory.getLogger(TaskanaConfigurationInitializer.class);
|
||||||
|
private static final String TASKANA_CUSTOM_HOLIDAY_DAY_MONTH_SEPARATOR = ".";
|
||||||
|
private static final String TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY =
|
||||||
|
"taskana.classification.categories";
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> splitStringAndTrimElements(String str, String separator) {
|
||||||
|
return splitStringAndTrimElements(str, separator, UnaryOperator.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> splitStringAndTrimElements(
|
||||||
|
String str, String separator, UnaryOperator<String> modifier) {
|
||||||
|
return Arrays.stream(str.split(Pattern.quote(separator)))
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.map(String::trim)
|
||||||
|
.map(modifier)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
static CustomHoliday createCustomHolidayFromPropsEntry(String customHolidayEntry)
|
||||||
|
throws WrongCustomHolidayFormatException {
|
||||||
|
List<String> parts =
|
||||||
|
splitStringAndTrimElements(customHolidayEntry, TASKANA_CUSTOM_HOLIDAY_DAY_MONTH_SEPARATOR);
|
||||||
|
if (parts.size() == 2) {
|
||||||
|
return CustomHoliday.of(Integer.valueOf(parts.get(0)), Integer.valueOf(parts.get(1)));
|
||||||
|
}
|
||||||
|
throw new WrongCustomHolidayFormatException(customHolidayEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, List<String>> configureClassificationCategoriesForType(
|
||||||
|
Properties props, List<String> classificationTypes) {
|
||||||
|
Function<String, List<String>> getClassificationCategoriesForType =
|
||||||
|
type ->
|
||||||
|
parseProperty(
|
||||||
|
props,
|
||||||
|
TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY + "." + type.toLowerCase(),
|
||||||
|
p -> splitStringAndTrimElements(p, ",", String::toUpperCase))
|
||||||
|
.orElseGet(ArrayList::new);
|
||||||
|
return classificationTypes.stream()
|
||||||
|
.map(type -> Pair.of(type, getClassificationCategoriesForType.apply(type)))
|
||||||
|
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<TaskanaRole, Set<String>> configureRoles(
|
||||||
|
String separator, Properties props, boolean shouldUseLowerCaseForAccessIds) {
|
||||||
|
Function<TaskanaRole, Set<String>> getAccessIdsForRole =
|
||||||
|
role ->
|
||||||
|
new HashSet<>(
|
||||||
|
splitStringAndTrimElements(
|
||||||
|
props.getProperty(role.getPropertyName().toLowerCase(), ""),
|
||||||
|
separator,
|
||||||
|
shouldUseLowerCaseForAccessIds
|
||||||
|
? String::toLowerCase
|
||||||
|
: UnaryOperator.identity()));
|
||||||
|
|
||||||
|
return Arrays.stream(TaskanaRole.values())
|
||||||
|
.map(role -> Pair.of(role, getAccessIdsForRole.apply(role)))
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setFieldValue(Object instance, Field field, Object value) {
|
||||||
|
final Optional<Method> hasSetterMethod =
|
||||||
|
Arrays.stream(instance.getClass().getMethods())
|
||||||
|
.filter(m -> m.getParameterCount() == 1)
|
||||||
|
.filter(m -> m.getName().startsWith("set"))
|
||||||
|
.filter(m -> m.getName().toLowerCase().contains(field.getName().toLowerCase()))
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (!hasSetterMethod.isPresent()) {
|
||||||
|
throw new SystemException("No setter method for " + field.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Method method = hasSetterMethod.get();
|
||||||
|
method.invoke(instance, value);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new SystemException(
|
||||||
|
"Property value " + value + " is invalid for field " + field.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package pro.taskana.common.internal.configuration;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface TaskanaProperty {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
package pro.taskana;
|
package pro.taskana;
|
||||||
|
|
||||||
|
import static pro.taskana.common.internal.configuration.TaskanaConfigurationInitializer.configureAnnotatedFields;
|
||||||
|
import static pro.taskana.common.internal.configuration.TaskanaConfigurationInitializer.configureClassificationCategoriesForType;
|
||||||
|
import static pro.taskana.common.internal.configuration.TaskanaConfigurationInitializer.configureRoles;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
@ -7,21 +11,13 @@ import java.sql.SQLException;
|
||||||
import java.time.Duration;
|
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.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.UnaryOperator;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import org.apache.ibatis.datasource.pooled.PooledDataSource;
|
import org.apache.ibatis.datasource.pooled.PooledDataSource;
|
||||||
|
@ -33,12 +29,10 @@ import pro.taskana.common.api.TaskanaEngine;
|
||||||
import pro.taskana.common.api.TaskanaEngine.ConnectionManagementMode;
|
import pro.taskana.common.api.TaskanaEngine.ConnectionManagementMode;
|
||||||
import pro.taskana.common.api.TaskanaRole;
|
import pro.taskana.common.api.TaskanaRole;
|
||||||
import pro.taskana.common.api.exceptions.SystemException;
|
import pro.taskana.common.api.exceptions.SystemException;
|
||||||
import pro.taskana.common.api.exceptions.WrongCustomHolidayFormatException;
|
|
||||||
import pro.taskana.common.internal.TaskanaEngineImpl;
|
import pro.taskana.common.internal.TaskanaEngineImpl;
|
||||||
import pro.taskana.common.internal.configuration.DB;
|
import pro.taskana.common.internal.configuration.DB;
|
||||||
import pro.taskana.common.internal.util.CheckedFunction;
|
import pro.taskana.common.internal.configuration.TaskanaProperty;
|
||||||
import pro.taskana.common.internal.util.FileLoaderUtil;
|
import pro.taskana.common.internal.util.FileLoaderUtil;
|
||||||
import pro.taskana.common.internal.util.Pair;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This central class creates the TaskanaEngine and holds all the information about DB and Security.
|
* This central class creates the TaskanaEngine and holds all the information about DB and Security.
|
||||||
|
@ -52,40 +46,10 @@ public class TaskanaEngineConfiguration {
|
||||||
|
|
||||||
private static final String TASKANA_PROPERTIES = "/taskana.properties";
|
private static final String TASKANA_PROPERTIES = "/taskana.properties";
|
||||||
private static final String TASKANA_PROPERTY_SEPARATOR = "|";
|
private static final String TASKANA_PROPERTY_SEPARATOR = "|";
|
||||||
private static final String TASKANA_JOB_BATCH_SIZE = "taskana.jobs.batchSize";
|
|
||||||
private static final String TASKANA_JOB_RETRIES = "taskana.jobs.maxRetries";
|
|
||||||
private static final String TASKANA_JOB_CLEANUP_RUN_EVERY = "taskana.jobs.cleanup.runEvery";
|
|
||||||
private static final String TASKANA_JOB_CLEANUP_FIRST_RUN = "taskana.jobs.cleanup.firstRunAt";
|
|
||||||
private static final String TASKANA_JOB_CLEANUP_MINIMUM_AGE = "taskana.jobs.cleanup.minimumAge";
|
|
||||||
private static final String TASKANA_JOB_TASK_CLEANUP_ALL_COMPLETED_SAME_PARENT_BUSINESS =
|
|
||||||
"taskana.jobs.cleanup.allCompletedSameParentBusiness";
|
|
||||||
private static final String TASKANA_JOB_PRIORITY_BATCHSIZE = "taskana.jobs.priority.batchSize";
|
|
||||||
private static final String TASKANA_JOB_PRIORITY_RUN_EVERY = "taskana.jobs.priority.runEvery";
|
|
||||||
private static final String TASKANA_JOB_PRIORITY_FIRST_RUN = "taskana.jobs.priority.firstRunAt";
|
|
||||||
private static final String TASKANA_JOB_PRIORITY_ACTIVE = "taskana.jobs.priority.active";
|
|
||||||
private static final String TASKANA_JOB_USER_REFRESH_FIRST_RUN =
|
|
||||||
"taskana.jobs.user.refresh.firstRunAt";
|
|
||||||
private static final String TASKANA_JOB_USER_REFRESH_RUN_EVERY =
|
|
||||||
"taskana.jobs.user.refresh.runEvery";
|
|
||||||
private static final String TASKANA_DOMAINS_PROPERTY = "taskana.domains";
|
|
||||||
private static final String TASKANA_CLASSIFICATION_TYPES_PROPERTY =
|
|
||||||
"taskana.classification.types";
|
|
||||||
private static final String TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY =
|
|
||||||
"taskana.classification.categories";
|
|
||||||
private static final String TASKANA_GERMAN_HOLIDAYS_ENABLED = "taskana.german.holidays.enabled";
|
|
||||||
private static final String TASKANA_GERMAN_HOLIDAYS_CORPUS_CHRISTI_ENABLED =
|
|
||||||
"taskana.german.holidays.corpus-christi.enabled";
|
|
||||||
private static final String TASKANA_CUSTOM_HOLIDAY = "taskana.custom.holidays";
|
|
||||||
private static final String TASKANA_CUSTOM_HOLIDAY_DAY_MONTH_SEPARATOR = ".";
|
|
||||||
private static final String TASKANA_HISTORY_DELETION_ON_TASK_DELETION_ENABLED =
|
|
||||||
"taskana.history.deletion.on.task.deletion.enabled";
|
|
||||||
private static final String TASKANA_VALIDATION_ALLOW_TIMESTAMP_SERVICE_LEVEL_MISMATCH =
|
|
||||||
"taskana.validation.allowTimestampServiceLevelMismatch";
|
|
||||||
private static final String TASKANA_ADD_ADDITIONAL_USER_INFO = "taskana.addAdditionalUserInfo";
|
|
||||||
// TASKANA_SCHEMA_VERSION
|
// TASKANA_SCHEMA_VERSION
|
||||||
private static final String DEFAULT_SCHEMA_NAME = "TASKANA";
|
private static final String DEFAULT_SCHEMA_NAME = "TASKANA";
|
||||||
|
|
||||||
private final List<CustomHoliday> customHolidays = new ArrayList<>();
|
|
||||||
// Taskana properties file
|
// Taskana properties file
|
||||||
protected String propertiesFileName = TASKANA_PROPERTIES;
|
protected String propertiesFileName = TASKANA_PROPERTIES;
|
||||||
// Taskana datasource configuration
|
// Taskana datasource configuration
|
||||||
|
@ -94,38 +58,79 @@ public class TaskanaEngineConfiguration {
|
||||||
// Taskana role configuration
|
// Taskana role configuration
|
||||||
protected String propertiesSeparator = TASKANA_PROPERTY_SEPARATOR;
|
protected String propertiesSeparator = TASKANA_PROPERTY_SEPARATOR;
|
||||||
protected Map<TaskanaRole, Set<String>> roleMap;
|
protected Map<TaskanaRole, Set<String>> roleMap;
|
||||||
|
|
||||||
// global switch to enable JAAS based authentication and Taskana
|
// global switch to enable JAAS based authentication and Taskana
|
||||||
// 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")
|
||||||
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")
|
||||||
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<>();
|
||||||
|
|
||||||
// Properties for the monitor
|
// Properties for the monitor
|
||||||
|
@TaskanaProperty("taskana.history.deletion.on.task.deletion.enabled")
|
||||||
private boolean deleteHistoryOnTaskDeletionEnabled;
|
private boolean deleteHistoryOnTaskDeletionEnabled;
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.german.holidays.enabled")
|
||||||
private boolean germanPublicHolidaysEnabled;
|
private boolean germanPublicHolidaysEnabled;
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.german.holidays.corpus-christi.enabled")
|
||||||
private boolean corpusChristiEnabled;
|
private boolean corpusChristiEnabled;
|
||||||
|
|
||||||
// Properties for general job execution
|
// Properties for general job execution
|
||||||
|
@TaskanaProperty("taskana.jobs.batchSize")
|
||||||
private int jobBatchSize = 100;
|
private int jobBatchSize = 100;
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.maxRetries")
|
||||||
private int maxNumberOfJobRetries = 3;
|
private int maxNumberOfJobRetries = 3;
|
||||||
|
|
||||||
// Properties for the cleanup job
|
// Properties for the cleanup job
|
||||||
|
@TaskanaProperty("taskana.jobs.cleanup.firstRunAt")
|
||||||
private Instant cleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
|
private Instant cleanupJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.cleanup.runEvery")
|
||||||
private Duration cleanupJobRunEvery = Duration.parse("P1D");
|
private Duration cleanupJobRunEvery = Duration.parse("P1D");
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.cleanup.minimumAge")
|
||||||
private Duration cleanupJobMinimumAge = Duration.parse("P14D");
|
private Duration cleanupJobMinimumAge = Duration.parse("P14D");
|
||||||
// TASKANA behavior
|
// TASKANA behavior
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.cleanup.allCompletedSameParentBusiness")
|
||||||
private boolean taskCleanupJobAllCompletedSameParentBusiness = true;
|
private boolean taskCleanupJobAllCompletedSameParentBusiness = true;
|
||||||
|
|
||||||
|
@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")
|
||||||
private boolean addAdditionalUserInfo = false;
|
private boolean addAdditionalUserInfo = false;
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.priority.batchSize")
|
||||||
private int priorityJobBatchSize = 100;
|
private int priorityJobBatchSize = 100;
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.priority.firstRunAt")
|
||||||
private Instant priorityJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
|
private Instant priorityJobFirstRun = Instant.parse("2018-01-01T00:00:00Z");
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.priority.runEvery")
|
||||||
private Duration priorityJobRunEvery = Duration.parse("P1D");
|
private Duration priorityJobRunEvery = Duration.parse("P1D");
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.priority.active")
|
||||||
private boolean priorityJobActive = false;
|
private boolean priorityJobActive = false;
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.user.refresh.runEvery")
|
||||||
private Duration userRefreshJobRunEvery = Duration.parse("P1D");
|
private Duration userRefreshJobRunEvery = Duration.parse("P1D");
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.jobs.user.refresh.firstRunAt")
|
||||||
private Instant userRefreshJobFirstRun = Instant.parse("2018-01-01T23:00:00Z");
|
private Instant userRefreshJobFirstRun = Instant.parse("2018-01-01T23:00:00Z");
|
||||||
|
|
||||||
public TaskanaEngineConfiguration(
|
public TaskanaEngineConfiguration(
|
||||||
|
@ -170,32 +175,50 @@ public class TaskanaEngineConfiguration {
|
||||||
initTaskanaProperties(this.propertiesFileName, this.propertiesSeparator);
|
initTaskanaProperties(this.propertiesFileName, this.propertiesSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Properties loadProperties(String propertiesFile) {
|
||||||
|
Properties props = new Properties();
|
||||||
|
try (InputStream stream =
|
||||||
|
FileLoaderUtil.openFileFromClasspathOrSystem(
|
||||||
|
propertiesFile, TaskanaEngineConfiguration.class)) {
|
||||||
|
props.load(stream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("caught IOException when processing properties file {}.", propertiesFile);
|
||||||
|
throw new SystemException(
|
||||||
|
"internal System error when processing properties file " + propertiesFile, e.getCause());
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
public void initTaskanaProperties(String propertiesFile, String separator) {
|
public void initTaskanaProperties(String propertiesFile, String separator) {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Reading taskana configuration from {} with separator {}", propertiesFile, separator);
|
"Reading taskana configuration from {} with separator {}", propertiesFile, separator);
|
||||||
}
|
}
|
||||||
Properties props = readPropertiesFromFile(propertiesFile);
|
Properties props = loadProperties(propertiesFile);
|
||||||
initTaskanaRoles(props, separator);
|
configureAnnotatedFields(this, separator, props);
|
||||||
initJobParameters(props);
|
roleMap = configureRoles(separator, props, shouldUseLowerCaseForAccessIds());
|
||||||
initDomains(props);
|
classificationCategoriesByTypeMap =
|
||||||
initClassificationTypes(props);
|
configureClassificationCategoriesForType(props, classificationTypes);
|
||||||
initClassificationCategories(props);
|
|
||||||
initBooleanProperty(
|
if (LOGGER.isDebugEnabled()) {
|
||||||
props, TASKANA_GERMAN_HOLIDAYS_ENABLED, this::setGermanPublicHolidaysEnabled);
|
roleMap.forEach((k, v) -> LOGGER.debug("Found Taskana RoleConfig {} : {} ", k, v));
|
||||||
initBooleanProperty(
|
LOGGER.debug(
|
||||||
props, TASKANA_GERMAN_HOLIDAYS_CORPUS_CHRISTI_ENABLED, this::setCorpusChristiEnabled);
|
"Configured number of task and workbasket updates per transaction: {}", jobBatchSize);
|
||||||
initBooleanProperty(
|
LOGGER.debug("Number of retries of failed task updates: {}", maxNumberOfJobRetries);
|
||||||
props,
|
LOGGER.debug("CleanupJob configuration: first run at {}", cleanupJobFirstRun);
|
||||||
TASKANA_HISTORY_DELETION_ON_TASK_DELETION_ENABLED,
|
LOGGER.debug("CleanupJob configuration: runs every {}", cleanupJobRunEvery);
|
||||||
this::setDeleteHistoryOnTaskDeletionEnabled);
|
LOGGER.debug(
|
||||||
initBooleanProperty(
|
"CleanupJob configuration: minimum age of tasks to be cleanup up is {}",
|
||||||
props,
|
cleanupJobMinimumAge);
|
||||||
TASKANA_VALIDATION_ALLOW_TIMESTAMP_SERVICE_LEVEL_MISMATCH,
|
LOGGER.debug(
|
||||||
this::setValidationAllowTimestampServiceLevelMismatch);
|
"TaskCleanupJob configuration: all completed task with the "
|
||||||
initBooleanProperty(
|
+ "same parent business property id {}",
|
||||||
props, TASKANA_ADD_ADDITIONAL_USER_INFO, this::setAddAdditionalUserInfo);
|
taskCleanupJobAllCompletedSameParentBusiness);
|
||||||
initCustomHolidays(props, separator);
|
LOGGER.debug("Configured classification categories : {}", classificationCategoriesByTypeMap);
|
||||||
|
LOGGER.debug("Configured domains: {}", domains);
|
||||||
|
LOGGER.debug("Configured classificationTypes: {}", classificationTypes);
|
||||||
|
LOGGER.debug("Configured custom Holidays : {}", customHolidays);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSource createDefaultDataSource() {
|
public static DataSource createDefaultDataSource() {
|
||||||
|
@ -310,6 +333,10 @@ public class TaskanaEngineConfiguration {
|
||||||
return customHolidays;
|
return customHolidays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCustomHolidays(List<CustomHoliday> customHolidays) {
|
||||||
|
this.customHolidays = new ArrayList<>(customHolidays);
|
||||||
|
}
|
||||||
|
|
||||||
public void addCustomHolidays(List<CustomHoliday> customHolidays) {
|
public void addCustomHolidays(List<CustomHoliday> customHolidays) {
|
||||||
this.customHolidays.addAll(customHolidays);
|
this.customHolidays.addAll(customHolidays);
|
||||||
}
|
}
|
||||||
|
@ -469,129 +496,7 @@ public class TaskanaEngineConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Properties readPropertiesFromFile() {
|
public Properties readPropertiesFromFile() {
|
||||||
return readPropertiesFromFile(this.propertiesFileName);
|
return loadProperties(this.propertiesFileName);
|
||||||
}
|
|
||||||
|
|
||||||
private <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 (Exception t) {
|
|
||||||
LOGGER.warn(
|
|
||||||
"Could not parse property {} ({}). Using default. Exception: {}",
|
|
||||||
key,
|
|
||||||
property,
|
|
||||||
t.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initJobParameters(Properties props) {
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_BATCH_SIZE, Integer::parseInt)
|
|
||||||
.ifPresent(this::setMaxNumberOfUpdatesPerTransaction);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_RETRIES, Integer::parseInt)
|
|
||||||
.ifPresent(this::setMaxNumberOfJobRetries);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_CLEANUP_FIRST_RUN, Instant::parse)
|
|
||||||
.ifPresent(this::setCleanupJobFirstRun);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_CLEANUP_RUN_EVERY, Duration::parse)
|
|
||||||
.ifPresent(this::setCleanupJobRunEvery);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_CLEANUP_MINIMUM_AGE, Duration::parse)
|
|
||||||
.ifPresent(this::setCleanupJobMinimumAge);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_PRIORITY_BATCHSIZE, Integer::parseInt)
|
|
||||||
.ifPresent(this::setPriorityJobBatchSize);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_PRIORITY_RUN_EVERY, Duration::parse)
|
|
||||||
.ifPresent(this::setPriorityJobRunEvery);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_PRIORITY_FIRST_RUN, Instant::parse)
|
|
||||||
.ifPresent(this::setPriorityJobFirstRun);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_PRIORITY_ACTIVE, Boolean::parseBoolean)
|
|
||||||
.ifPresent(this::setPriorityJobActive);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_USER_REFRESH_RUN_EVERY, Duration::parse)
|
|
||||||
.ifPresent(this::setUserRefreshJobRunEvery);
|
|
||||||
|
|
||||||
parseProperty(props, TASKANA_JOB_USER_REFRESH_FIRST_RUN, Instant::parse)
|
|
||||||
.ifPresent(this::setUserRefreshJobFirstRun);
|
|
||||||
|
|
||||||
parseProperty(
|
|
||||||
props,
|
|
||||||
TASKANA_JOB_TASK_CLEANUP_ALL_COMPLETED_SAME_PARENT_BUSINESS,
|
|
||||||
Boolean::parseBoolean)
|
|
||||||
.ifPresent(this::setTaskCleanupJobAllCompletedSameParentBusiness);
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug(
|
|
||||||
"Configured number of task and workbasket updates per transaction: {}", jobBatchSize);
|
|
||||||
LOGGER.debug("Number of retries of failed task updates: {}", maxNumberOfJobRetries);
|
|
||||||
LOGGER.debug("CleanupJob configuration: first run at {}", cleanupJobFirstRun);
|
|
||||||
LOGGER.debug("CleanupJob configuration: runs every {}", cleanupJobRunEvery);
|
|
||||||
LOGGER.debug(
|
|
||||||
"CleanupJob configuration: minimum age of tasks to be cleanup up is {}",
|
|
||||||
cleanupJobMinimumAge);
|
|
||||||
LOGGER.debug(
|
|
||||||
"TaskCleanupJob configuration: all completed task with the "
|
|
||||||
+ "same parent business property id {}",
|
|
||||||
taskCleanupJobAllCompletedSameParentBusiness);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDomains(Properties props) {
|
|
||||||
CheckedFunction<String, List<String>, Exception> parseFunction =
|
|
||||||
p -> splitStringAndTrimElements(p, ",", String::toUpperCase);
|
|
||||||
parseProperty(props, TASKANA_DOMAINS_PROPERTY, parseFunction).ifPresent(this::setDomains);
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("Configured domains: {}", domains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initClassificationTypes(Properties props) {
|
|
||||||
CheckedFunction<String, List<String>, Exception> parseFunction =
|
|
||||||
p -> splitStringAndTrimElements(p, ",", String::toUpperCase);
|
|
||||||
parseProperty(props, TASKANA_CLASSIFICATION_TYPES_PROPERTY, parseFunction)
|
|
||||||
.ifPresent(this::setClassificationTypes);
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("Configured classificationTypes: {}", classificationTypes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initClassificationCategories(Properties props) {
|
|
||||||
Function<String, List<String>> getClassificationCategoriesForType =
|
|
||||||
type -> {
|
|
||||||
CheckedFunction<String, List<String>, Exception> parseFunction =
|
|
||||||
s -> splitStringAndTrimElements(s, ",", String::toUpperCase);
|
|
||||||
return parseProperty(
|
|
||||||
props,
|
|
||||||
TASKANA_CLASSIFICATION_CATEGORIES_PROPERTY + "." + type.toLowerCase(),
|
|
||||||
parseFunction)
|
|
||||||
.orElseGet(ArrayList::new);
|
|
||||||
};
|
|
||||||
|
|
||||||
classificationCategoriesByTypeMap =
|
|
||||||
classificationTypes.stream()
|
|
||||||
.map(type -> Pair.of(type, getClassificationCategoriesForType.apply(type)))
|
|
||||||
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("Configured classification categories : {}", classificationCategoriesByTypeMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initBooleanProperty(
|
|
||||||
Properties props, String propertyName, Consumer<Boolean> consumer) {
|
|
||||||
parseProperty(props, propertyName, Boolean::parseBoolean).ifPresent(consumer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSchemaName(String schemaName) {
|
private void initSchemaName(String schemaName) {
|
||||||
|
@ -617,85 +522,4 @@ public class TaskanaEngineConfiguration {
|
||||||
LOGGER.debug("Using schema name {}", this.getSchemaName());
|
LOGGER.debug("Using schema name {}", this.getSchemaName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initTaskanaRoles(Properties props, String rolesSeparator) {
|
|
||||||
Function<TaskanaRole, Set<String>> getAccessIdsForRole =
|
|
||||||
role -> {
|
|
||||||
List<String> accessIds =
|
|
||||||
splitStringAndTrimElements(
|
|
||||||
props.getProperty(role.getPropertyName().toLowerCase(), ""),
|
|
||||||
rolesSeparator,
|
|
||||||
shouldUseLowerCaseForAccessIds()
|
|
||||||
? String::toLowerCase
|
|
||||||
: UnaryOperator.identity());
|
|
||||||
return new HashSet<>(accessIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
roleMap =
|
|
||||||
Arrays.stream(TaskanaRole.values())
|
|
||||||
.map(role -> Pair.of(role, getAccessIdsForRole.apply(role)))
|
|
||||||
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
roleMap.forEach((k, v) -> LOGGER.debug("Found Taskana RoleConfig {} : {} ", k, v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initCustomHolidays(Properties props, String separator) {
|
|
||||||
CheckedFunction<String, List<CustomHoliday>, Exception> parseFunction =
|
|
||||||
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, TASKANA_CUSTOM_HOLIDAY, parseFunction).ifPresent(this::addCustomHolidays);
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("Configured custom Holidays : {}", customHolidays);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CustomHoliday createCustomHolidayFromPropsEntry(String customHolidayEntry)
|
|
||||||
throws WrongCustomHolidayFormatException {
|
|
||||||
List<String> parts =
|
|
||||||
splitStringAndTrimElements(customHolidayEntry, TASKANA_CUSTOM_HOLIDAY_DAY_MONTH_SEPARATOR);
|
|
||||||
if (parts.size() == 2) {
|
|
||||||
return CustomHoliday.of(Integer.valueOf(parts.get(0)), Integer.valueOf(parts.get(1)));
|
|
||||||
}
|
|
||||||
throw new WrongCustomHolidayFormatException(customHolidayEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> splitStringAndTrimElements(String str, String separator) {
|
|
||||||
return splitStringAndTrimElements(str, separator, UnaryOperator.identity());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> splitStringAndTrimElements(
|
|
||||||
String str, String separator, UnaryOperator<String> modifier) {
|
|
||||||
return Arrays.stream(str.split(Pattern.quote(separator)))
|
|
||||||
.filter(s -> !s.isEmpty())
|
|
||||||
.map(String::trim)
|
|
||||||
.map(modifier)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Properties readPropertiesFromFile(String propertiesFile) {
|
|
||||||
Properties props = new Properties();
|
|
||||||
try (InputStream stream =
|
|
||||||
FileLoaderUtil.openFileFromClasspathOrSystem(propertiesFile, getClass())) {
|
|
||||||
props.load(stream);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.error("caught IOException when processing properties file {}.", propertiesFile);
|
|
||||||
throw new SystemException(
|
|
||||||
"internal System error when processing properties file " + propertiesFile, e.getCause());
|
|
||||||
}
|
|
||||||
return props;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue