> createDockerContainer(DB db) {
+ switch (db) {
+ case ORACLE:
+ return Optional.of(
+ new OracleContainer("gvenzl/oracle-xe:18-slim-faststart")
+ .withDatabaseName("kadai")
+ .withUsername("TEST_USER")
+ .withPassword("testPassword")
+ .withEnv("TZ", "Europe/Berlin"));
+ case DB2:
+ return Optional.of(
+ new Db2Container(
+ DockerImageName.parse("taskana/db2:11.5")
+ .asCompatibleSubstituteFor("ibmcom/db2"))
+ .waitingFor(
+ new LogMessageWaitStrategy()
+ .withRegEx(".*DB2START processing was successful.*")
+ .withStartupTimeout(Duration.of(60, SECONDS)))
+ .withUsername("db2inst1")
+ .withPassword("db2inst1-pwd")
+ .withDatabaseName("TSKDB"));
+ case POSTGRES:
+ return Optional.of(
+ new PostgreSQLContainer<>(DockerImageName.parse("postgres:14.7"))
+ .withUsername("postgres")
+ .withPassword("postgres")
+ .withDatabaseName("postgres")
+ .withCommand(
+ "/bin/sh",
+ "-c",
+ "localedef -i de_DE -c -f UTF-8 -A /usr/share/locale/locale.alias de_DE.UTF-8 "
+ + "&& export LANG=de_DE.UTF-8 "
+ + "&& /usr/local/bin/docker-entrypoint.sh postgres -c fsync=off")
+ .waitingFor(
+ new LogMessageWaitStrategy()
+ .withRegEx(".*Datenbanksystem ist bereit, um Verbindungen anzunehmen.*\\s")
+ .withTimes(2)
+ .withStartupTimeout(Duration.of(60, SECONDS))));
+ default:
+ return Optional.empty();
+ }
+ }
+
+ public static DataSource createDataSource(JdbcDatabaseContainer> container) {
+ PooledDataSource ds =
+ new PooledDataSource(
+ Thread.currentThread().getContextClassLoader(),
+ container.getDriverClassName(),
+ container.getJdbcUrl(),
+ container.getUsername(),
+ container.getPassword());
+ ds.setPoolTimeToWait(50);
+ ds.forceCloseAll(); // otherwise, the MyBatis pool is not initialized correctly
+ return ds;
+ }
+}
diff --git a/common/kadai-common-test/src/main/java/io/kadai/common/test/OracleSchemaHelper.java b/common/kadai-common-test/src/main/java/io/kadai/common/test/OracleSchemaHelper.java
new file mode 100644
index 0000000000..9788cd7b55
--- /dev/null
+++ b/common/kadai-common-test/src/main/java/io/kadai/common/test/OracleSchemaHelper.java
@@ -0,0 +1,68 @@
+package io.kadai.common.test;
+
+import io.kadai.common.api.exceptions.SystemException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import javax.sql.DataSource;
+
+public final class OracleSchemaHelper {
+
+ private static final String DEFAULT_PASSWORD = "testPassword";
+
+ private OracleSchemaHelper() {
+ // hide implicitpublic one
+ }
+
+ public static void initOracleSchema(DataSource dataSource, String schemaName)
+ throws SystemException {
+ try (Connection connection = dataSource.getConnection();
+ // connect as SYSTEM user to create schemas
+ Connection conn =
+ DriverManager.getConnection(
+ connection.getMetaData().getURL(), "SYSTEM", DEFAULT_PASSWORD);
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("GRANT ALL PRIVILEGES TO TEST_USER");
+
+ dropExistingSchema(schemaName, stmt);
+
+ stmt.addBatch(
+ String.format(
+ "create tablespace %s datafile '%s.dat' size 5M autoextend "
+ + "on NEXT 5M MAXSIZE UNLIMITED",
+ schemaName, schemaName));
+ stmt.addBatch(
+ String.format(
+ "create temporary tablespace %s_TMP tempfile '%s_tmp.dat' size 5M autoextend "
+ + "on NEXT 5M MAXSIZE UNLIMITED",
+ schemaName, schemaName));
+ stmt.addBatch(
+ String.format(
+ "create user %s identified by %s default tablespace %s "
+ + "temporary tablespace %s_TMP",
+ schemaName, DEFAULT_PASSWORD, schemaName, schemaName));
+ stmt.addBatch(String.format("ALTER USER %s quota unlimited on %s", schemaName, schemaName));
+ stmt.addBatch(String.format("GRANT UNLIMITED TABLESPACE TO %s", schemaName));
+ stmt.executeBatch();
+ } catch (Exception e) {
+ throw new SystemException("Failed to setup ORACLE Schema", e);
+ }
+ }
+
+ private static void dropExistingSchema(String schemaName, Statement stmt) {
+ try {
+ stmt.addBatch(String.format("DROP USER %s CASCADE", schemaName));
+ stmt.addBatch(
+ String.format(
+ "DROP TABLESPACE %s_TMP INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS",
+ schemaName));
+ stmt.addBatch(
+ String.format(
+ "DROP TABLESPACE %s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS",
+ schemaName));
+ stmt.executeBatch();
+ } catch (Exception e) {
+ // IGNORE exceptions from dropping
+ }
+ }
+}
diff --git a/common/taskana-common-test/src/main/java/pro/taskana/common/test/config/DataSourceGenerator.java b/common/kadai-common-test/src/main/java/io/kadai/common/test/config/DataSourceGenerator.java
similarity index 82%
rename from common/taskana-common-test/src/main/java/pro/taskana/common/test/config/DataSourceGenerator.java
rename to common/kadai-common-test/src/main/java/io/kadai/common/test/config/DataSourceGenerator.java
index 7d8fd16512..15bc3f1e02 100644
--- a/common/taskana-common-test/src/main/java/pro/taskana/common/test/config/DataSourceGenerator.java
+++ b/common/kadai-common-test/src/main/java/io/kadai/common/test/config/DataSourceGenerator.java
@@ -1,19 +1,19 @@
-package pro.taskana.common.test.config;
+package io.kadai.common.test.config;
-import static pro.taskana.common.test.OracleSchemaHelper.initOracleSchema;
+import static io.kadai.common.test.OracleSchemaHelper.initOracleSchema;
+import io.kadai.common.internal.configuration.DB;
+import io.kadai.common.test.DockerContainerCreator;
import java.util.Optional;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.testcontainers.containers.JdbcDatabaseContainer;
-import pro.taskana.common.internal.configuration.DB;
-import pro.taskana.common.test.DockerContainerCreator;
/**
* The DataSourceGenerator provides the proper {@linkplain DataSource} for all Integration tests.
*
* Additionally the property schemaName can be defined. If that property is missing, or
- * the file doesn't exist the schemaName TASKANA will be used.
+ * the file doesn't exist the schemaName KADAI will be used.
*/
public final class DataSourceGenerator {
@@ -52,8 +52,8 @@ public static String getSchemaName() {
public static DataSource createDataSourceForH2() {
String jdbcDriver = "org.h2.Driver";
String jdbcUrl =
- "jdbc:h2:mem:taskana;NON_KEYWORDS=KEY,VALUE;LOCK_MODE=0;"
- + "INIT=CREATE SCHEMA IF NOT EXISTS TASKANA\\;"
+ "jdbc:h2:mem:kadai;NON_KEYWORDS=KEY,VALUE;LOCK_MODE=0;"
+ + "INIT=CREATE SCHEMA IF NOT EXISTS KADAI\\;"
+ "SET COLLATION DEFAULT_de_DE ";
String dbUserName = "sa";
String dbPassword = "sa";
@@ -71,7 +71,7 @@ public static DataSource createDataSourceForH2() {
}
private static String determineSchemaName(DB db) {
- return db == DB.POSTGRES ? "taskana" : "TASKANA";
+ return db == DB.POSTGRES ? "kadai" : "KADAI";
}
private static DB retrieveDatabaseFromEnv() {
diff --git a/common/kadai-common-test/src/main/java/io/kadai/common/test/security/JaasExtension.java b/common/kadai-common-test/src/main/java/io/kadai/common/test/security/JaasExtension.java
new file mode 100644
index 0000000000..5e55f17e7a
--- /dev/null
+++ b/common/kadai-common-test/src/main/java/io/kadai/common/test/security/JaasExtension.java
@@ -0,0 +1,331 @@
+package io.kadai.common.test.security;
+
+import static io.kadai.common.internal.util.CheckedFunction.wrapExceptFor;
+import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;
+
+import io.kadai.common.api.exceptions.SystemException;
+import io.kadai.common.api.security.GroupPrincipal;
+import io.kadai.common.api.security.UserPrincipal;
+import io.kadai.common.test.security.WithAccessId.WithAccessIds;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import javax.security.auth.Subject;
+import org.junit.jupiter.api.DynamicContainer;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.extension.DynamicTestInvocationContext;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
+import org.junit.jupiter.api.extension.ExtensionContext.Store;
+import org.junit.jupiter.api.extension.InvocationInterceptor;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.commons.support.AnnotationSupport;
+import org.opentest4j.TestAbortedException;
+
+/** Runner for integration tests that enables JAAS subject. */
+public class JaasExtension implements InvocationInterceptor, TestTemplateInvocationContextProvider {
+
+ private static final String ACCESS_IDS_STORE_KEY = "accessIds";
+
+ // region InvocationInterceptor
+
+ @Override
+ public T interceptTestClassConstructor(
+ Invocation invocation,
+ ReflectiveInvocationContext> invocationContext,
+ ExtensionContext extensionContext) {
+ return extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ @Override
+ public void interceptBeforeAllMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ @Override
+ public void interceptBeforeEachMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ @Override
+ public void interceptTestMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ if (isAnnotated(invocationContext.getExecutable(), WithAccessIds.class)) {
+ throw new JUnitException("Please use @TestTemplate instead of @Test for multiple accessIds");
+ }
+ extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T interceptTestFactoryMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ WithAccessIds annotation = invocationContext.getExecutable().getAnnotation(WithAccessIds.class);
+ if (annotation != null) {
+ // our goal is to run each test returned from the test factory X times. X is the amount of
+ // WithAccessId annotations. In order to achieve this we are wrapping the result from the
+ // factory (the returning tests) in a dynamicContainer for each accessId. Since we don't know
+ // what the factory will return we have to check for every possible return type. All possible
+ // return types can be found here:
+ // https://junit.org/junit5/docs/current/user-guide/#writing-tests-dynamic-tests
+ // After checking each return type we abuse the return type of T and hardly change it to
+ // Stream no matter what the factory returns. This return type is allowed
+ // per definition (See link above), but is not the type T. Hence we have an unchecked cast at
+ // the end to keep the compiler happy...
+
+ // we are using the first annotation to run the factory method with.
+ T factoryResult = performInvocationWithAccessId(invocation, annotation.value()[0]);
+
+ Iterable newChildrenForDynamicContainer;
+ // TestFactory must have one of the following return types. See link above for further details
+ if (factoryResult instanceof DynamicNode) {
+ newChildrenForDynamicContainer = Collections.singleton((DynamicNode) factoryResult);
+ } else if (factoryResult instanceof Stream) {
+ Stream nodes = (Stream) factoryResult;
+ newChildrenForDynamicContainer = nodes.toList();
+ } else if (factoryResult instanceof Iterable) {
+ newChildrenForDynamicContainer = (Iterable) factoryResult;
+ } else if (factoryResult instanceof Iterator) {
+ newChildrenForDynamicContainer = () -> (Iterator) factoryResult;
+ } else if (factoryResult instanceof DynamicNode[]) {
+ newChildrenForDynamicContainer = Arrays.asList((DynamicNode[]) factoryResult);
+ } else {
+ throw new SystemException(
+ String.format(
+ "Testfactory '%s' did not return a proper type",
+ invocationContext.getExecutable().getName()));
+ }
+
+ // Currently, a DynamicContainer has children from this type: Stream
+ // Because of this the children can only be extracted once (Streams can only be operated
+ // once). This is obviously not ok since we want to execute each node X times. So we have to
+ // manually insert all children recursively to extract them X times...
+ Map> childrenMap = new HashMap<>();
+ persistDynamicContainerChildren(newChildrenForDynamicContainer, childrenMap);
+
+ Function wrapTestsInDynamicContainer =
+ accessId ->
+ DynamicContainer.dynamicContainer(
+ getDisplayNameForAccessId(accessId),
+ StreamSupport.stream(newChildrenForDynamicContainer.spliterator(), false)
+ .map(x -> duplicateDynamicNode(x, childrenMap)));
+
+ Store store = getMethodLevelStore(extensionContext);
+ return (T)
+ Stream.of(annotation.value())
+ .map(
+ a -> {
+ store.put(ACCESS_IDS_STORE_KEY, a);
+ return wrapTestsInDynamicContainer.apply(a);
+ });
+ }
+
+ return extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ @Override
+ public void interceptTestTemplateMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ WithAccessId accessId =
+ getMethodLevelStore(extensionContext).get(ACCESS_IDS_STORE_KEY, WithAccessId.class);
+ performInvocationWithAccessId(invocation, accessId);
+ }
+
+ @Override
+ public void interceptDynamicTest(
+ Invocation invocation,
+ DynamicTestInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ ExtensionContext testContext = getParentMethodExtensionContent(extensionContext);
+ // Check if the test factory provided an access Id for this dynamic test.
+ WithAccessId o = getMethodLevelStore(testContext).get(ACCESS_IDS_STORE_KEY, WithAccessId.class);
+ if (o != null) {
+ performInvocationWithAccessId(invocation, o);
+ } else {
+ extractAccessIdAndPerformInvocation(invocation, testContext.getRequiredTestMethod());
+ }
+ }
+
+ @Override
+ public void interceptAfterEachMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ @Override
+ public void interceptAfterAllMethod(
+ Invocation invocation,
+ ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) {
+ extractAccessIdAndPerformInvocation(invocation, invocationContext.getExecutable());
+ }
+
+ // endregion
+
+ // region TestTemplateInvocationContextProvider
+
+ @Override
+ public boolean supportsTestTemplate(ExtensionContext context) {
+ return isAnnotated(context.getElement(), WithAccessIds.class)
+ || isAnnotated(context.getElement(), WithAccessId.class);
+ }
+
+ @Override
+ public Stream provideTestTemplateInvocationContexts(
+ ExtensionContext context) {
+ List accessIds =
+ AnnotationSupport.findRepeatableAnnotations(context.getElement(), WithAccessId.class);
+ Store store = getMethodLevelStore(context);
+ return accessIds.stream()
+ .map(
+ a -> {
+ store.put(ACCESS_IDS_STORE_KEY, a);
+ return new JaasExtensionInvocationContext(a);
+ });
+ }
+
+ // endregion
+
+ private static void persistDynamicContainerChildren(
+ Iterable nodes, Map> childrenMap) {
+ nodes.forEach(
+ node -> {
+ if (node instanceof DynamicContainer) {
+ DynamicContainer container = (DynamicContainer) node;
+ List children = container.getChildren().collect(Collectors.toList());
+ childrenMap.put(container.hashCode() + container.getDisplayName(), children);
+ persistDynamicContainerChildren(children, childrenMap);
+ }
+ });
+ }
+
+ private static DynamicNode duplicateDynamicNode(
+ DynamicNode node, Map> lookupMap) {
+ if (node instanceof DynamicContainer) {
+ DynamicContainer container = (DynamicContainer) node;
+ Stream children =
+ lookupMap.get(node.hashCode() + node.getDisplayName()).stream()
+ .map(x -> duplicateDynamicNode(x, lookupMap));
+ return DynamicContainer.dynamicContainer(container.getDisplayName(), children);
+ }
+ return node;
+ }
+
+ private static T extractAccessIdAndPerformInvocation(
+ Invocation invocation, AnnotatedElement executable) {
+ return performInvocationWithAccessId(invocation, executable.getAnnotation(WithAccessId.class));
+ }
+
+ private static T performInvocationWithAccessId(
+ Invocation invocation, WithAccessId withAccessId) {
+ Subject subject = new Subject();
+ subject.getPrincipals().addAll(getPrincipals(withAccessId));
+
+ Function, T> proceedInvocation =
+ wrapExceptFor(Invocation::proceed, TestAbortedException.class);
+ PrivilegedAction performInvocation = () -> proceedInvocation.apply(invocation);
+ return Subject.doAs(subject, performInvocation);
+ }
+
+ private static List getPrincipals(WithAccessId withAccessId) {
+ if (withAccessId != null) {
+ return Stream.concat(
+ Stream.of(withAccessId.user()).map(UserPrincipal::new),
+ Arrays.stream(withAccessId.groups()).map(GroupPrincipal::new))
+ .toList();
+ }
+ return Collections.emptyList();
+ }
+
+ private ExtensionContext getParentMethodExtensionContent(ExtensionContext extensionContext) {
+ Optional parent = extensionContext.getParent();
+ // the class MethodExtensionContext is part of junit-jupiter-engine and has only a
+ // package-private visibility thus this workaround is needed.
+ while (!parent
+ .map(Object::getClass)
+ .map(Class::getName)
+ .filter(s -> s.endsWith("MethodExtensionContext"))
+ .isPresent()) {
+ parent = parent.flatMap(ExtensionContext::getParent);
+ }
+ return parent.orElseThrow(
+ () ->
+ new JUnitException(
+ String.format(
+ "Test '%s' does not have a parent method", extensionContext.getUniqueId())));
+ }
+
+ private static Store getMethodLevelStore(ExtensionContext context) {
+ return context.getStore(
+ Namespace.create(context.getRequiredTestClass(), context.getRequiredTestMethod()));
+ }
+
+ private static String getDisplayNameForAccessId(WithAccessId withAccessId) {
+ return String.format("for user '%s'", withAccessId.user());
+ }
+
+ private static class JaasExtensionInvocationContext implements TestTemplateInvocationContext {
+ private final WithAccessId withAccessId;
+
+ private JaasExtensionInvocationContext(WithAccessId withAccessId) {
+ this.withAccessId = withAccessId;
+ }
+
+ @Override
+ public String getDisplayName(int invocationIndex) {
+ return getDisplayNameForAccessId(withAccessId);
+ }
+
+ @Override
+ public List getAdditionalExtensions() {
+ return Collections.singletonList(new WithAccessIdParameterResolver());
+ }
+
+ private class WithAccessIdParameterResolver implements ParameterResolver {
+ @Override
+ public boolean supportsParameter(
+ ParameterContext parameterContext, ExtensionContext extensionContext) {
+ return parameterContext.getParameter().getType().equals(WithAccessId.class);
+ }
+
+ @Override
+ public Object resolveParameter(
+ ParameterContext parameterContext, ExtensionContext extensionContext) {
+ return withAccessId;
+ }
+ }
+ }
+}
diff --git a/common/kadai-common-test/src/main/java/io/kadai/common/test/security/WithAccessId.java b/common/kadai-common-test/src/main/java/io/kadai/common/test/security/WithAccessId.java
new file mode 100644
index 0000000000..e20d6ded3d
--- /dev/null
+++ b/common/kadai-common-test/src/main/java/io/kadai/common/test/security/WithAccessId.java
@@ -0,0 +1,26 @@
+package io.kadai.common.test.security;
+
+import io.kadai.common.test.security.WithAccessId.WithAccessIds;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Repeatable(WithAccessIds.class)
+public @interface WithAccessId {
+
+ String user();
+
+ String[] groups() default {};
+
+ String[] permissions() default {};
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @interface WithAccessIds {
+ WithAccessId[] value();
+ }
+}
diff --git a/common/taskana-common-test/src/main/java/pro/taskana/common/test/util/ParallelThreadHelper.java b/common/kadai-common-test/src/main/java/io/kadai/common/test/util/ParallelThreadHelper.java
similarity index 90%
rename from common/taskana-common-test/src/main/java/pro/taskana/common/test/util/ParallelThreadHelper.java
rename to common/kadai-common-test/src/main/java/io/kadai/common/test/util/ParallelThreadHelper.java
index 09cbab7604..7cb705feb3 100644
--- a/common/taskana-common-test/src/main/java/pro/taskana/common/test/util/ParallelThreadHelper.java
+++ b/common/kadai-common-test/src/main/java/io/kadai/common/test/util/ParallelThreadHelper.java
@@ -1,9 +1,9 @@
-package pro.taskana.common.test.util;
+package io.kadai.common.test.util;
+import io.kadai.common.api.exceptions.SystemException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
-import pro.taskana.common.api.exceptions.SystemException;
public class ParallelThreadHelper {
diff --git a/common/taskana-common-test/src/main/java/org/junit/rules/TestRule.java b/common/kadai-common-test/src/main/java/org/junit/rules/TestRule.java
similarity index 100%
rename from common/taskana-common-test/src/main/java/org/junit/rules/TestRule.java
rename to common/kadai-common-test/src/main/java/org/junit/rules/TestRule.java
diff --git a/common/taskana-common-test/src/main/java/org/junit/runners/model/Statement.java b/common/kadai-common-test/src/main/java/org/junit/runners/model/Statement.java
similarity index 100%
rename from common/taskana-common-test/src/main/java/org/junit/runners/model/Statement.java
rename to common/kadai-common-test/src/main/java/org/junit/runners/model/Statement.java
diff --git a/common/taskana-common-test/src/main/resources/container-license-acceptance.txt b/common/kadai-common-test/src/main/resources/container-license-acceptance.txt
similarity index 100%
rename from common/taskana-common-test/src/main/resources/container-license-acceptance.txt
rename to common/kadai-common-test/src/main/resources/container-license-acceptance.txt
diff --git a/common/kadai-common-test/src/test/java/io/kadai/common/test/security/JaasExtensionTest.java b/common/kadai-common-test/src/test/java/io/kadai/common/test/security/JaasExtensionTest.java
new file mode 100644
index 0000000000..53e01ca7ec
--- /dev/null
+++ b/common/kadai-common-test/src/test/java/io/kadai/common/test/security/JaasExtensionTest.java
@@ -0,0 +1,735 @@
+package io.kadai.common.test.security;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+import io.kadai.common.api.security.CurrentUserContext;
+import io.kadai.common.internal.security.CurrentUserContextImpl;
+import io.kadai.common.test.security.JaasExtensionTestExtensions.ShouldThrowJunitException;
+import io.kadai.common.test.security.JaasExtensionTestExtensions.ShouldThrowParameterResolutionException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DynamicContainer;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(JaasExtension.class)
+class JaasExtensionTest {
+
+ private static final String INSIDE_DYNAMIC_TEST_USER = "inside_dynamic_test";
+ private static final CurrentUserContext CURRENT_USER_CONTEXT = new CurrentUserContextImpl(true);
+ private static final DynamicTest NOT_NULL_DYNAMIC_TEST =
+ dynamicTest("dynamic test", () -> assertThat(CURRENT_USER_CONTEXT.getUserid()).isNotNull());
+ private static final DynamicTest NULL_DYNAMIC_TEST =
+ dynamicTest("dynamic test", () -> assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull());
+ private static final DynamicTest DYNAMIC_TEST_USER_DYNAMIC_TEST =
+ dynamicTest(
+ "dynamic test",
+ () -> assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo(INSIDE_DYNAMIC_TEST_USER));
+
+ // region JaasExtension#interceptBeforeAllMethod
+
+ @BeforeAll
+ static void should_NotSetJaasSubject_When_AnnotationIsMissing_On_BeforeAll() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ @WithAccessId(user = "beforeall")
+ @BeforeAll
+ static void should_SetJaasSubject_When_AnnotationExists_On_BeforeAll() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("beforeall");
+ }
+
+ @WithAccessId(user = "beforeall")
+ @WithAccessId(user = "beforeall2")
+ @BeforeAll
+ static void should_NotSetJaasSubject_When_MultipleAnnotationsExist_On_BeforeAll() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptBeforeEachMethod
+
+ @BeforeEach
+ void should_NotSetJaasSubject_When_AnnotationIsMissing_On_BeforeEach() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ @WithAccessId(user = "beforeeach")
+ @BeforeEach
+ void should_SetJaasSubject_When_AnnotationExists_On_BeforeEach() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("beforeeach");
+ }
+
+ @WithAccessId(user = "beforeeach")
+ @WithAccessId(user = "beforeeach2")
+ @BeforeEach
+ void should_NotSetJaasSubject_When_MultipleAnnotationsExist_On_BeforeEach() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptAfterEachMethod
+
+ @AfterEach
+ void should_NotSetJaasSubject_When_AnnotationIsMissing_On_AfterEach() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ @WithAccessId(user = "aftereach")
+ @AfterEach
+ void should_SetJaasSubject_When_AnnotationExists_On_AfterEach() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("aftereach");
+ }
+
+ @WithAccessId(user = "aftereach")
+ @WithAccessId(user = "afterach2")
+ @AfterEach
+ void should_NotSetJaasSubject_When_MultipleAnnotationsExist_On_AfterEach() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptAfterAllMethod
+
+ @AfterAll
+ static void should_NotSetJaasSubject_When_AnnotationIsMissing_On_AfterAll() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ @WithAccessId(user = "afterall")
+ @AfterAll
+ static void should_SetJaasSubject_When_AnnotationExists_On_AfterAll() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("afterall");
+ }
+
+ @WithAccessId(user = "afterall")
+ @WithAccessId(user = "afterall2")
+ @AfterAll
+ static void should_NotSetJaasSubject_When_MultipleAnnotationsExist_On_AfterAll() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptTestMethod
+
+ @Test
+ void should_NotSetJaasSubject_When_AnnotationIsMissing_On_Test() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ @WithAccessId(user = "user")
+ @Test
+ void should_SetJaasSubject_When_AnnotationExists_On_Test() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("user");
+ assertThat(CURRENT_USER_CONTEXT.getGroupIds()).isEmpty();
+ }
+
+ @WithAccessId(
+ user = "user",
+ groups = {"group1", "group2"})
+ @Test
+ void should_SetJaasSubjectWithGroups_When_AnnotationExistsWithGroups_On_Test() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("user");
+ assertThat(CURRENT_USER_CONTEXT.getGroupIds()).containsExactlyInAnyOrder("group1", "group2");
+ }
+
+ @WithAccessId(user = "user")
+ @Test
+ @ExtendWith(ShouldThrowParameterResolutionException.class)
+ void should_NotInjectParameter_When_TestTemplateIsNotUsed(
+ @SuppressWarnings("unused") WithAccessId accessId) {
+ // THIS IS NOT RELEVANT
+ assertThat(true).isTrue();
+ }
+
+ @WithAccessId(user = "user")
+ @WithAccessId(user = "user2")
+ @Test
+ @ExtendWith(ShouldThrowJunitException.class)
+ void should_ThrowJunitException_When_MultipleAnnotationsExist_On_Test() {
+ // THIS IS NOT RELEVANT
+ assertThat(true).isTrue();
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptTestFactory
+
+ @TestFactory
+ List should_NotSetJaasSubject_When_AnnotationIsMissing_On_TestFactory() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ return List.of();
+ }
+
+ @WithAccessId(user = "testfactory")
+ @TestFactory
+ List should_SetJaasSubject_When_AnnotationExists_On_TestFactory() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("testfactory");
+ return List.of();
+ }
+
+ @WithAccessId(user = "testfactory1")
+ @WithAccessId(user = "testfactory2")
+ @TestFactory
+ List
+ should_SetJaasSubjectFromFirstAnnotation_When_MultipleAnnotationsExists_On_TestFactory() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("testfactory1");
+ return List.of();
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptTestTemplateMethod
+
+ @WithAccessId(user = "testtemplate")
+ @TestTemplate
+ void should_SetJaasSubject_When_AnnotationExists_On_TestTemplate() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("testtemplate");
+ }
+
+ @WithAccessId(user = "testtemplate1")
+ @WithAccessId(user = "testtemplate2")
+ @WithAccessId(user = "testtemplate3")
+ @TestTemplate
+ void should_SetMultipleJaasSubjects_When_MultipleAnnotationsExist_On_TestTemplate(
+ WithAccessId accessId) {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo(accessId.user());
+ }
+
+ @WithAccessId(user = "testtemplate1", groups = "abc", permissions = "perm")
+ @TestTemplate
+ void should_InjectCorrectAccessId_When_AnnotationExists_On_TestTemplate(WithAccessId accessId) {
+ assertThat(accessId.user()).isEqualTo("testtemplate1");
+ assertThat(accessId.groups()).containsExactly("abc");
+ assertThat(accessId.permissions()).containsExactly("perm");
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptDynamicTest
+
+ // region RETURNING DynamicNode
+
+ // WITH DynamicTest
+
+ @TestFactory
+ DynamicTest should_NotSetAccessIdForDynamicTest_When_AnnotationIsMissing() {
+ return NULL_DYNAMIC_TEST;
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicTest should_SetAccessIdForDynamicTest_When_AnnotationExists() {
+ return DYNAMIC_TEST_USER_DYNAMIC_TEST;
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicTest should_SetMultipleAccessIdForDynamicTest_When_AnnotationsExist() {
+ return NOT_NULL_DYNAMIC_TEST;
+ }
+
+ // WITH DynamicContainer
+
+ @TestFactory
+ DynamicContainer should_NotSetAccessIdForDynamicContainer_When_AnnotationIsMissing() {
+ return dynamicContainer("dynamic container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer should_SetAccessIdForDynamicContainer_When_AnnotationExists() {
+ return dynamicContainer(
+ "dynamic container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer should_SetMultipleAccessIdForDynamicContainer_When_AnnotationsExist() {
+ return dynamicContainer(
+ "dynamic container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ }
+
+ // WITH nested DynamicContainer
+
+ @TestFactory
+ DynamicContainer should_NotSetAccessIdForNestedDynamicContainer_When_AnnotationIsMissing() {
+ DynamicContainer container =
+ dynamicContainer("inside container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ return dynamicContainer("outside container", Stream.of(container, NULL_DYNAMIC_TEST));
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer should_SetAccessIdForNestedDynamicContainer_When_AnnotationExists() {
+ DynamicContainer container =
+ dynamicContainer(
+ "nested container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return dynamicContainer(
+ "outside container", Stream.of(container, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer should_SetMultipleAccessIdForNestedDynamicContainer_When_AnnotationsExist() {
+ DynamicContainer container =
+ dynamicContainer(
+ "inside container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ return dynamicContainer("outside container", Stream.of(container, NOT_NULL_DYNAMIC_TEST));
+ }
+
+ // endregion
+
+ // region RETURNING Stream
+
+ @TestFactory
+ Stream should_NotSetAccessIdForDynamicTestInStream_When_AnnotationIsMissing() {
+ return Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Stream should_SetAccessIdForDynamicTestInStream_When_AnnotationExists() {
+ return Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Stream should_SetMultipleAccessIdForDynamicTestInStream_When_AnnotationsExist() {
+ return Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST);
+ }
+
+ // WITH DynamicContainer
+
+ @TestFactory
+ Stream
+ should_NotSetAccessIdForDynamicContainerInStream_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () ->
+ dynamicContainer("dynamic container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Stream should_SetAccessIdForDynamicContainerInStream_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Stream
+ should_SetMultipleAccessIdForDynamicContainerInStream_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2);
+ }
+
+ // WITH nested DynamicContainer
+
+ @TestFactory
+ Stream
+ should_NotSetAccessIdForNestedDynamicContainerInStream_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () -> dynamicContainer("inside container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () -> dynamicContainer("outside container", Stream.of(supplier.get(), NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Stream
+ should_SetAccessIdForNestedDynamicContainerInStream_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer(
+ "outside container", Stream.of(supplier.get(), DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Stream
+ should_SetMultipleAccessIdForNestedDynamicContainerInStream_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer("outside container", Stream.of(supplier.get(), NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2);
+ }
+
+ // endregion
+
+ // region RETURNING Iterable
+
+ @TestFactory
+ Iterable should_NotSetAccessIdForDynamicTestInIterable_When_AnnotationIsMissing() {
+ return Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST).toList();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterable should_SetAccessIdForDynamicTestInIterable_When_AnnotationExists() {
+ return Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST)
+ .toList();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterable should_SetMultipleAccessIdForDynamicTestInIterable_When_AnnotationsExist() {
+ return Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST).toList();
+ }
+
+ // WITH DynamicContainer
+
+ @TestFactory
+ Iterable
+ should_NotSetAccessIdForDynamicContainerInIterable_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () ->
+ dynamicContainer("dynamic container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).toList();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterable
+ should_SetAccessIdForDynamicContainerInIterable_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).toList();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterable
+ should_SetMultipleAccessIdForDynamicContainerInIterable_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).toList();
+ }
+
+ // WITH nested DynamicContainer
+
+ @TestFactory
+ Iterable
+ should_NotSetAccessIdForNestedDynamicContainerInIterable_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () -> dynamicContainer("inside container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () -> dynamicContainer("outside container", Stream.of(supplier.get(), NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).toList();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterable
+ should_SetAccessIdForNestedDynamicContainerInIterable_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer(
+ "outside container", Stream.of(supplier.get(), DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).toList();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterable
+ should_SetMultipleAccessIdForNestedDynamicContainerInIterable_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer("outside container", Stream.of(supplier.get(), NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).toList();
+ }
+
+ // endregion
+
+ // region RETURNING Iterator
+
+ @TestFactory
+ Iterator should_NotSetAccessIdForDynamicTestInIterator_When_AnnotationIsMissing() {
+ return Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST).iterator();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterator should_SetAccessIdForDynamicTestInIterator_When_AnnotationExists() {
+ return Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST).iterator();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterator should_SetMultipleAccessIdForDynamicTestInIterator_When_AnnotationsExist() {
+ return Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST).iterator();
+ }
+
+ // WITH DynamicContainer
+
+ @TestFactory
+ Iterator
+ should_NotSetAccessIdForDynamicContainerInIterator_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () ->
+ dynamicContainer("dynamic container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).iterator();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterator
+ should_SetAccessIdForDynamicContainerInIterator_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).iterator();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterator
+ should_SetMultipleAccessIdForDynamicContainerInIterator_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).iterator();
+ }
+
+ // WITH nested DynamicContainer
+
+ @TestFactory
+ Iterator
+ should_NotSetAccessIdForNestedDynamicContainerInIterator_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () -> dynamicContainer("inside container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () -> dynamicContainer("outside container", Stream.of(supplier.get(), NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).iterator();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterator
+ should_SetAccessIdForNestedDynamicContainerInIterator_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer(
+ "outside container", Stream.of(supplier.get(), DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).iterator();
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ Iterator
+ should_SetMultipleAccessIdForNestedDynamicContainerInIterator_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer("outside container", Stream.of(supplier.get(), NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).iterator();
+ }
+
+ // endregion
+
+ // region RETURNING DynamicNode[]
+
+ @TestFactory
+ DynamicTest[] should_NotSetAccessIdForDynamicTestInArray_When_AnnotationIsMissing() {
+ return Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST).toArray(DynamicTest[]::new);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicTest[] should_SetAccessIdForDynamicTestInArray_When_AnnotationExists() {
+ return Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST)
+ .toArray(DynamicTest[]::new);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicTest[] should_SetMultipleAccessIdForDynamicTestInArray_When_AnnotationsExist() {
+ return Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST).toArray(DynamicTest[]::new);
+ }
+
+ // WITH DynamicContainer
+
+ @TestFactory
+ DynamicContainer[] should_NotSetAccessIdForDynamicContainerInArray_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () ->
+ dynamicContainer("dynamic container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).toArray(DynamicContainer[]::new);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer[] should_SetAccessIdForDynamicContainerInArray_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).toArray(DynamicContainer[]::new);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer[] should_SetMultipleAccessIdForDynamicContainerInArray_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "dynamic container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(supplier).limit(2).toArray(DynamicContainer[]::new);
+ }
+
+ // WITH nested DynamicContainer
+
+ @TestFactory
+ DynamicContainer[]
+ should_NotSetAccessIdForNestedDynamicContainerInArray_When_AnnotationIsMissing() {
+ Supplier supplier =
+ () -> dynamicContainer("inside container", Stream.of(NULL_DYNAMIC_TEST, NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () -> dynamicContainer("outside container", Stream.of(supplier.get(), NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).toArray(DynamicContainer[]::new);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer[] should_SetAccessIdForNestedDynamicContainerInArray_When_AnnotationExists() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container",
+ Stream.of(DYNAMIC_TEST_USER_DYNAMIC_TEST, DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer(
+ "outside container", Stream.of(supplier.get(), DYNAMIC_TEST_USER_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).toArray(DynamicContainer[]::new);
+ }
+
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @WithAccessId(user = INSIDE_DYNAMIC_TEST_USER)
+ @TestFactory
+ DynamicContainer[]
+ should_SetMultipleAccessIdForNestedDynamicContainerInArray_When_AnnotationsExist() {
+ Supplier supplier =
+ () ->
+ dynamicContainer(
+ "inside container", Stream.of(NOT_NULL_DYNAMIC_TEST, NOT_NULL_DYNAMIC_TEST));
+ Supplier outsideSupplier =
+ () ->
+ dynamicContainer("outside container", Stream.of(supplier.get(), NOT_NULL_DYNAMIC_TEST));
+ return Stream.generate(outsideSupplier).limit(2).toArray(DynamicContainer[]::new);
+ }
+
+ // endregion
+
+ // region JaasExtension#interceptTestClassConstructor
+
+ @Nested
+ @TestInstance(Lifecycle.PER_CLASS)
+ class ConstructorWithoutAccessId {
+ ConstructorWithoutAccessId() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+
+ @Test
+ void should_NotSetJaasSubject_When_AnnotationIsMissing_On_Constructor() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+ }
+
+ @Nested
+ @TestInstance(Lifecycle.PER_CLASS)
+ class ConstructorWithAccessId {
+ @WithAccessId(user = "constructor")
+ ConstructorWithAccessId() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("constructor");
+ }
+
+ @Test
+ void should_SetJaasSubject_When_AnnotationExists_On_Constructor() {
+ assertThat(CURRENT_USER_CONTEXT.getUserid()).isNull();
+ }
+ }
+
+ // endregion
+
+ // endregion
+}
diff --git a/common/kadai-common-test/src/test/java/io/kadai/common/test/security/JaasExtensionTestExtensions.java b/common/kadai-common-test/src/test/java/io/kadai/common/test/security/JaasExtensionTestExtensions.java
new file mode 100644
index 0000000000..d7d1d375e3
--- /dev/null
+++ b/common/kadai-common-test/src/test/java/io/kadai/common/test/security/JaasExtensionTestExtensions.java
@@ -0,0 +1,37 @@
+package io.kadai.common.test.security;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
+import org.junit.platform.commons.JUnitException;
+
+public class JaasExtensionTestExtensions {
+ static class ShouldThrowParameterResolutionException implements TestExecutionExceptionHandler {
+
+ @Override
+ public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
+ throws Throwable {
+ if (throwable instanceof ParameterResolutionException) {
+ return;
+ }
+ throw throwable;
+ }
+ }
+
+ static class ShouldThrowJunitException implements TestExecutionExceptionHandler {
+
+ @Override
+ public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
+ throws Throwable {
+ if (throwable instanceof JUnitException) {
+ JUnitException exception = (JUnitException) throwable;
+ assertThat(exception.getMessage())
+ .isEqualTo("Please use @TestTemplate instead of @Test for multiple accessIds");
+ return;
+ }
+ throw throwable;
+ }
+ }
+}
diff --git a/common/kadai-common/pom.xml b/common/kadai-common/pom.xml
new file mode 100644
index 0000000000..5cf2f2b302
--- /dev/null
+++ b/common/kadai-common/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+ kadai-common
+
+ ${project.groupId}:${project.artifactId}
+ The global helper module
+
+
+ io.kadai
+ kadai-common-parent
+ 8.2.1-SNAPSHOT
+
+
+
+
+ io.kadai
+ kadai-common-logging
+ ${project.version}
+
+
+ io.kadai
+ kadai-common-security
+ ${project.version}
+
+
+ org.json
+ json
+ ${version.json}
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.mybatis
+ mybatis
+ ${version.mybatis}
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.assertj
+ assertj-core
+ ${version.assertj}
+ test
+
+
+
+
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/BaseQuery.java b/common/kadai-common/src/main/java/io/kadai/common/api/BaseQuery.java
similarity index 99%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/BaseQuery.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/BaseQuery.java
index d5d90b5ed4..1bb4a86c52 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/BaseQuery.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/BaseQuery.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
import java.util.Arrays;
import java.util.List;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/BulkOperationResults.java b/common/kadai-common/src/main/java/io/kadai/common/api/BulkOperationResults.java
similarity index 98%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/BulkOperationResults.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/BulkOperationResults.java
index 67c0603e1e..a5b50f8596 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/BulkOperationResults.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/BulkOperationResults.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/CustomHoliday.java b/common/kadai-common/src/main/java/io/kadai/common/api/CustomHoliday.java
similarity index 96%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/CustomHoliday.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/CustomHoliday.java
index 0a7b93e15b..c0509f3ab1 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/CustomHoliday.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/CustomHoliday.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
import java.util.Objects;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/IntInterval.java b/common/kadai-common/src/main/java/io/kadai/common/api/IntInterval.java
similarity index 79%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/IntInterval.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/IntInterval.java
index adabce0959..7403a224f7 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/IntInterval.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/IntInterval.java
@@ -1,6 +1,6 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
-import pro.taskana.common.internal.Interval;
+import io.kadai.common.internal.Interval;
/**
* IntInterval captures an Integer interval. A fixed interval has defined begin and end. An open
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/KadaiRole.java b/common/kadai-common/src/main/java/io/kadai/common/api/KadaiRole.java
new file mode 100644
index 0000000000..266f6497e3
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/KadaiRole.java
@@ -0,0 +1,11 @@
+package io.kadai.common.api;
+
+/** The KadaiRole enum contains all roles that are known to KADAI. */
+public enum KadaiRole {
+ USER,
+ BUSINESS_ADMIN,
+ ADMIN,
+ MONITOR,
+ TASK_ADMIN,
+ TASK_ROUTER
+}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/KeyDomain.java b/common/kadai-common/src/main/java/io/kadai/common/api/KeyDomain.java
similarity index 96%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/KeyDomain.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/KeyDomain.java
index cb224e1379..1e86f04452 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/KeyDomain.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/KeyDomain.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
import java.util.Objects;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/LocalTimeInterval.java b/common/kadai-common/src/main/java/io/kadai/common/api/LocalTimeInterval.java
similarity index 92%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/LocalTimeInterval.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/LocalTimeInterval.java
index 8ead568355..2e0332a7b5 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/LocalTimeInterval.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/LocalTimeInterval.java
@@ -1,8 +1,8 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
+import io.kadai.common.internal.Interval;
import java.time.LocalTime;
import java.util.Objects;
-import pro.taskana.common.internal.Interval;
/**
* LocalTimeInterval provides a closed interval using {@link LocalTime}.
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/QueryColumnName.java b/common/kadai-common/src/main/java/io/kadai/common/api/QueryColumnName.java
similarity index 80%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/QueryColumnName.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/QueryColumnName.java
index 69813087a3..173518366b 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/QueryColumnName.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/QueryColumnName.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
/** This interface is used to emulate an extensible enum for use in the Basequery interface. */
public interface QueryColumnName {}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/TimeInterval.java b/common/kadai-common/src/main/java/io/kadai/common/api/TimeInterval.java
similarity index 80%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/TimeInterval.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/TimeInterval.java
index 155cc19eb5..87d91c969e 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/TimeInterval.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/TimeInterval.java
@@ -1,7 +1,7 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
+import io.kadai.common.internal.Interval;
import java.time.Instant;
-import pro.taskana.common.internal.Interval;
/**
* Capture a time interval. A fixed interval has defined begin and end Instant. An open ended
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/WorkingTimeCalculator.java b/common/kadai-common/src/main/java/io/kadai/common/api/WorkingTimeCalculator.java
similarity index 97%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/WorkingTimeCalculator.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/WorkingTimeCalculator.java
index 853fda9049..e423f27a8a 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/WorkingTimeCalculator.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/WorkingTimeCalculator.java
@@ -1,8 +1,8 @@
-package pro.taskana.common.api;
+package io.kadai.common.api;
+import io.kadai.common.api.exceptions.InvalidArgumentException;
import java.time.Duration;
import java.time.Instant;
-import pro.taskana.common.api.exceptions.InvalidArgumentException;
@SuppressWarnings("unused")
public interface WorkingTimeCalculator {
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/AutocommitFailedException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/AutocommitFailedException.java
new file mode 100644
index 0000000000..7aa8ed8a64
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/AutocommitFailedException.java
@@ -0,0 +1,14 @@
+package io.kadai.common.api.exceptions;
+
+/**
+ * This exception is thrown when using KADAI with the AUTOCOMMIT ConnectionManagementMode and an
+ * attempt to commit fails.
+ */
+public class AutocommitFailedException extends KadaiRuntimeException {
+
+ public static final String ERROR_KEY = "CONNECTION_AUTOCOMMIT_FAILED";
+
+ public AutocommitFailedException(Throwable cause) {
+ super("Autocommit failed", ErrorCode.of(ERROR_KEY), cause);
+ }
+}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/ConcurrencyException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ConcurrencyException.java
similarity index 86%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/ConcurrencyException.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ConcurrencyException.java
index a7c59f2c78..f9dcebedfb 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/ConcurrencyException.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ConcurrencyException.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api.exceptions;
+package io.kadai.common.api.exceptions;
import java.util.Map;
@@ -6,7 +6,7 @@
* This exception is thrown when an attempt is made to update an object that has already been
* updated by another user.
*/
-public class ConcurrencyException extends TaskanaException {
+public class ConcurrencyException extends KadaiException {
public static final String ERROR_KEY = "ENTITY_NOT_UP_TO_DATE";
private final String entityId;
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ConnectionNotSetException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ConnectionNotSetException.java
new file mode 100644
index 0000000000..0d54353225
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ConnectionNotSetException.java
@@ -0,0 +1,15 @@
+package io.kadai.common.api.exceptions;
+
+/**
+ * This exception is thrown when using KADAI with the EXPLICIT ConnectionManagementMode and an
+ * attempt is made to call an API method before the KadainEngine#setConnection() method has been
+ * called.
+ */
+public class ConnectionNotSetException extends KadaiRuntimeException {
+
+ public static final String ERROR_KEY = "CONNECTION_NOT_SET";
+
+ public ConnectionNotSetException() {
+ super("Connection not set", ErrorCode.of(ERROR_KEY));
+ }
+}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/DomainNotFoundException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/DomainNotFoundException.java
similarity index 82%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/DomainNotFoundException.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/exceptions/DomainNotFoundException.java
index 717e2d5b85..1560e3a2cc 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/DomainNotFoundException.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/DomainNotFoundException.java
@@ -1,9 +1,9 @@
-package pro.taskana.common.api.exceptions;
+package io.kadai.common.api.exceptions;
import java.util.Map;
/** This exception is thrown when the specified domain is not found in the configuration. */
-public class DomainNotFoundException extends TaskanaException {
+public class DomainNotFoundException extends KadaiException {
public static final String ERROR_KEY = "DOMAIN_NOT_FOUND";
private final String domain;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/ErrorCode.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ErrorCode.java
similarity index 97%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/ErrorCode.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ErrorCode.java
index 6a0a76110b..06dd0480f0 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/ErrorCode.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/ErrorCode.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.api.exceptions;
+package io.kadai.common.api.exceptions;
import java.io.Serializable;
import java.util.Collections;
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/InvalidArgumentException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/InvalidArgumentException.java
new file mode 100644
index 0000000000..ae402b5ebb
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/InvalidArgumentException.java
@@ -0,0 +1,15 @@
+package io.kadai.common.api.exceptions;
+
+/** This exception is thrown when a method is called with an invalid argument. */
+public class InvalidArgumentException extends KadaiRuntimeException {
+
+ public static final String ERROR_KEY = "INVALID_ARGUMENT";
+
+ public InvalidArgumentException(String msg) {
+ this(msg, null);
+ }
+
+ public InvalidArgumentException(String msg, Throwable cause) {
+ super(msg, ErrorCode.of(ERROR_KEY), cause);
+ }
+}
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/KadaiException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/KadaiException.java
new file mode 100644
index 0000000000..d1050613ad
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/KadaiException.java
@@ -0,0 +1,36 @@
+package io.kadai.common.api.exceptions;
+
+import java.io.Serializable;
+
+/** common base class for KADAI's checked exceptions. */
+public class KadaiException extends Exception {
+
+ private final ErrorCode errorCode;
+
+ protected KadaiException(String message, ErrorCode errorCode) {
+ this(message, errorCode, null);
+ }
+
+ protected KadaiException(String message, ErrorCode errorCode, Throwable cause) {
+ super(message, cause);
+ this.errorCode = errorCode;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ protected static Serializable ensureNullIsHandled(Serializable o) {
+ return o == null ? "null" : o;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + " [errorCode="
+ + errorCode
+ + ", message="
+ + getMessage()
+ + "]";
+ }
+}
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/KadaiRuntimeException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/KadaiRuntimeException.java
new file mode 100644
index 0000000000..c3ad948ce6
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/KadaiRuntimeException.java
@@ -0,0 +1,36 @@
+package io.kadai.common.api.exceptions;
+
+import java.io.Serializable;
+
+/** The common base class for KADAI's runtime exceptions. */
+public class KadaiRuntimeException extends RuntimeException {
+
+ private final ErrorCode errorCode;
+
+ protected KadaiRuntimeException(String message, ErrorCode errorCode) {
+ this(message, errorCode, null);
+ }
+
+ protected KadaiRuntimeException(String message, ErrorCode errorCode, Throwable cause) {
+ super(message, cause);
+ this.errorCode = errorCode;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ protected static Serializable ensureNullIsHandled(Serializable o) {
+ return o == null ? "null" : o;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + " [errorCode="
+ + errorCode
+ + ", message="
+ + getMessage()
+ + "]";
+ }
+}
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/NotAuthorizedException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/NotAuthorizedException.java
new file mode 100644
index 0000000000..2f954ec720
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/NotAuthorizedException.java
@@ -0,0 +1,39 @@
+package io.kadai.common.api.exceptions;
+
+import io.kadai.common.api.KadaiRole;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * This exception is thrown when the current user is not in a certain {@linkplain KadaiRole role}
+ * it is supposed to be.
+ */
+public class NotAuthorizedException extends KadaiException {
+
+ public static final String ERROR_KEY = "NOT_AUTHORIZED";
+ private final String currentUserId;
+ private final KadaiRole[] roles;
+
+ public NotAuthorizedException(String currentUserId, KadaiRole... roles) {
+ super(
+ String.format(
+ "Not authorized. The current user '%s' is not member of role(s) '%s'.",
+ currentUserId, Arrays.toString(roles)),
+ ErrorCode.of(
+ ERROR_KEY,
+ Map.ofEntries(
+ Map.entry("roles", ensureNullIsHandled(roles)),
+ Map.entry("currentUserId", ensureNullIsHandled(currentUserId)))));
+
+ this.currentUserId = currentUserId;
+ this.roles = roles;
+ }
+
+ public KadaiRole[] getRoles() {
+ return roles;
+ }
+
+ public String getCurrentUserId() {
+ return currentUserId;
+ }
+}
diff --git a/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/SystemException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/SystemException.java
new file mode 100644
index 0000000000..d7e39c646f
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/SystemException.java
@@ -0,0 +1,15 @@
+package io.kadai.common.api.exceptions;
+
+/** This exception is thrown when a generic KADAI problem is encountered. */
+public class SystemException extends KadaiRuntimeException {
+
+ public static final String ERROR_KEY = "CRITICAL_SYSTEM_ERROR";
+
+ public SystemException(String msg) {
+ this(msg, null);
+ }
+
+ public SystemException(String msg, Throwable cause) {
+ super(msg, ErrorCode.of(ERROR_KEY), cause);
+ }
+}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/UnsupportedDatabaseException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/UnsupportedDatabaseException.java
similarity index 84%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/UnsupportedDatabaseException.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/exceptions/UnsupportedDatabaseException.java
index 12d470493e..87a67f5bb1 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/UnsupportedDatabaseException.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/UnsupportedDatabaseException.java
@@ -1,11 +1,11 @@
-package pro.taskana.common.api.exceptions;
+package io.kadai.common.api.exceptions;
import java.util.Map;
/**
* This exception is thrown when the database name doesn't match to one of the desired databases.
*/
-public class UnsupportedDatabaseException extends TaskanaRuntimeException {
+public class UnsupportedDatabaseException extends KadaiRuntimeException {
public static final String ERROR_KEY = "DATABASE_UNSUPPORTED";
private final String databaseProductName;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/WrongCustomHolidayFormatException.java b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/WrongCustomHolidayFormatException.java
similarity index 80%
rename from common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/WrongCustomHolidayFormatException.java
rename to common/kadai-common/src/main/java/io/kadai/common/api/exceptions/WrongCustomHolidayFormatException.java
index 0f8b1bfd1e..51d8921836 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/api/exceptions/WrongCustomHolidayFormatException.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/api/exceptions/WrongCustomHolidayFormatException.java
@@ -1,10 +1,10 @@
-package pro.taskana.common.api.exceptions;
+package io.kadai.common.api.exceptions;
+import io.kadai.common.api.CustomHoliday;
import java.util.Map;
-import pro.taskana.common.api.CustomHoliday;
/** This exception is thrown when an entry for the {@linkplain CustomHoliday} has a wrong format. */
-public class WrongCustomHolidayFormatException extends TaskanaRuntimeException {
+public class WrongCustomHolidayFormatException extends KadaiRuntimeException {
public static final String ERROR_KEY = "CUSTOM_HOLIDAY_WRONG_FORMAT";
private final String customHoliday;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/Interval.java b/common/kadai-common/src/main/java/io/kadai/common/internal/Interval.java
similarity index 97%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/Interval.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/Interval.java
index bdbf4d2a3e..3eb6a3ac68 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/Interval.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/Interval.java
@@ -1,4 +1,4 @@
-package pro.taskana.common.internal;
+package io.kadai.common.internal;
import java.util.Objects;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/DB.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/DB.java
similarity index 80%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/DB.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/configuration/DB.java
index 0bacfa9cfb..ee53fe8780 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/DB.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/DB.java
@@ -1,25 +1,25 @@
-package pro.taskana.common.internal.configuration;
+package io.kadai.common.internal.configuration;
+import io.kadai.common.api.exceptions.SystemException;
+import io.kadai.common.api.exceptions.UnsupportedDatabaseException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
-import pro.taskana.common.api.exceptions.SystemException;
-import pro.taskana.common.api.exceptions.UnsupportedDatabaseException;
/** Supported versions of databases. */
public enum DB {
- H2("H2", "h2", "/sql/h2/schema-detection-h2.sql", "/sql/h2/taskana-schema-h2.sql"),
- DB2("DB2", "db2", "/sql/db2/schema-detection-db2.sql", "/sql/db2/taskana-schema-db2.sql"),
+ H2("H2", "h2", "/sql/h2/schema-detection-h2.sql", "/sql/h2/kadai-schema-h2.sql"),
+ DB2("DB2", "db2", "/sql/db2/schema-detection-db2.sql", "/sql/db2/kadai-schema-db2.sql"),
ORACLE(
"Oracle",
"oracle",
"/sql/oracle/schema-detection-oracle.sql",
- "/sql/oracle/taskana-schema-oracle.sql"),
+ "/sql/oracle/kadai-schema-oracle.sql"),
POSTGRES(
"PostgreSQL",
"postgres",
"/sql/postgres/schema-detection-postgres.sql",
- "/sql/postgres/taskana-schema-postgres.sql");
+ "/sql/postgres/kadai-schema-postgres.sql");
public final String dbProductName;
public final String dbProductId;
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/DbSchemaCreator.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/DbSchemaCreator.java
similarity index 93%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/DbSchemaCreator.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/configuration/DbSchemaCreator.java
index e79c231693..4b8ac9424a 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/DbSchemaCreator.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/DbSchemaCreator.java
@@ -1,5 +1,6 @@
-package pro.taskana.common.internal.configuration;
+package io.kadai.common.internal.configuration;
+import io.kadai.common.internal.util.ComparableVersion;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -17,9 +18,8 @@
import org.apache.ibatis.jdbc.SqlRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import pro.taskana.common.internal.util.ComparableVersion;
-/** This class create the schema for taskana. */
+/** This class create the schema for kadai. */
public class DbSchemaCreator {
private static final Logger LOGGER = LoggerFactory.getLogger(DbSchemaCreator.class);
@@ -80,8 +80,8 @@ public boolean isValidSchemaVersion(String expectedMinVersion) {
}
String query =
- "select VERSION from TASKANA_SCHEMA_VERSION where "
- + "VERSION = (select max(VERSION) from TASKANA_SCHEMA_VERSION) ";
+ "select VERSION from KADAI_SCHEMA_VERSION where "
+ + "VERSION = (select max(VERSION) from KADAI_SCHEMA_VERSION) ";
Map queryResult = runner.selectOne(query);
@@ -90,7 +90,7 @@ public boolean isValidSchemaVersion(String expectedMinVersion) {
if (actualVersion.compareTo(minVersion) < 0) {
LOGGER.error(
- "Schema version not valid. The VERSION property in table TASKANA_SCHEMA_VERSION "
+ "Schema version not valid. The VERSION property in table KADAI_SCHEMA_VERSION "
+ "has not the expected min value {}",
expectedMinVersion);
return false;
@@ -103,7 +103,7 @@ public boolean isValidSchemaVersion(String expectedMinVersion) {
} catch (RuntimeSqlException | SQLException e) {
LOGGER.error(
- "Schema version not valid. The VERSION property in table TASKANA_SCHEMA_VERSION "
+ "Schema version not valid. The VERSION property in table KADAI_SCHEMA_VERSION "
+ "has not the expected min value {}",
expectedMinVersion);
return false;
diff --git a/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/KadaiProperty.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/KadaiProperty.java
new file mode 100644
index 0000000000..40dca7589f
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/KadaiProperty.java
@@ -0,0 +1,13 @@
+package io.kadai.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 KadaiProperty {
+
+ String value();
+}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/CollectionPropertyParser.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/CollectionPropertyParser.java
similarity index 91%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/CollectionPropertyParser.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/CollectionPropertyParser.java
index 52c59e916b..fc50b1c8a3 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/CollectionPropertyParser.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/CollectionPropertyParser.java
@@ -1,13 +1,13 @@
-package pro.taskana.common.internal.configuration.parser;
+package io.kadai.common.internal.configuration.parser;
+import io.kadai.common.api.exceptions.SystemException;
+import io.kadai.common.internal.util.ReflectionUtil;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Pattern;
-import pro.taskana.common.api.exceptions.SystemException;
-import pro.taskana.common.internal.util.ReflectionUtil;
public class CollectionPropertyParser> implements PropertyParser {
diff --git a/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/CustomHolidayParser.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/CustomHolidayParser.java
new file mode 100644
index 0000000000..34f4432f2a
--- /dev/null
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/CustomHolidayParser.java
@@ -0,0 +1,19 @@
+package io.kadai.common.internal.configuration.parser;
+
+import io.kadai.common.api.CustomHoliday;
+import io.kadai.common.api.exceptions.WrongCustomHolidayFormatException;
+import java.util.List;
+
+public class CustomHolidayParser extends SimpleParser {
+ public CustomHolidayParser() {
+ super(CustomHoliday.class, CustomHolidayParser::parse);
+ }
+
+ private static CustomHoliday parse(String value) {
+ List parts = splitStringAndTrimElements(value, ".");
+ if (parts.size() == 2) {
+ return CustomHoliday.of(Integer.valueOf(parts.get(0)), Integer.valueOf(parts.get(1)));
+ }
+ throw new WrongCustomHolidayFormatException(value);
+ }
+}
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/EnumPropertyParser.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/EnumPropertyParser.java
similarity index 93%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/EnumPropertyParser.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/EnumPropertyParser.java
index 042552df73..0d50852264 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/EnumPropertyParser.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/EnumPropertyParser.java
@@ -1,5 +1,6 @@
-package pro.taskana.common.internal.configuration.parser;
+package io.kadai.common.internal.configuration.parser;
+import io.kadai.common.api.exceptions.SystemException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
@@ -7,7 +8,6 @@
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
-import pro.taskana.common.api.exceptions.SystemException;
public class EnumPropertyParser implements PropertyParser> {
@Override
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/LocalTimeIntervalParser.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/LocalTimeIntervalParser.java
similarity index 82%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/LocalTimeIntervalParser.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/LocalTimeIntervalParser.java
index b7f8c3e6f6..e572a90f5f 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/LocalTimeIntervalParser.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/LocalTimeIntervalParser.java
@@ -1,9 +1,9 @@
-package pro.taskana.common.internal.configuration.parser;
+package io.kadai.common.internal.configuration.parser;
+import io.kadai.common.api.LocalTimeInterval;
+import io.kadai.common.api.exceptions.SystemException;
import java.time.LocalTime;
import java.util.List;
-import pro.taskana.common.api.LocalTimeInterval;
-import pro.taskana.common.api.exceptions.SystemException;
public class LocalTimeIntervalParser extends SimpleParser {
public LocalTimeIntervalParser() {
diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/MapPropertyParser.java b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/MapPropertyParser.java
similarity index 88%
rename from common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/MapPropertyParser.java
rename to common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/MapPropertyParser.java
index e0ac0c1392..5e8ed68ae0 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/configuration/parser/MapPropertyParser.java
+++ b/common/kadai-common/src/main/java/io/kadai/common/internal/configuration/parser/MapPropertyParser.java
@@ -1,5 +1,9 @@
-package pro.taskana.common.internal.configuration.parser;
+package io.kadai.common.internal.configuration.parser;
+import io.kadai.common.api.exceptions.SystemException;
+import io.kadai.common.internal.configuration.KadaiProperty;
+import io.kadai.common.internal.util.Pair;
+import io.kadai.common.internal.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -7,10 +11,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
-import pro.taskana.common.api.exceptions.SystemException;
-import pro.taskana.common.internal.configuration.TaskanaProperty;
-import pro.taskana.common.internal.util.Pair;
-import pro.taskana.common.internal.util.ReflectionUtil;
public class MapPropertyParser implements PropertyParser