Compare commits

...

9 Commits

Author SHA1 Message Date
Mustapha Zorgati 44e11c37b8 Updated poms to version 4.7.4-SNAPSHOT 2021-08-19 20:26:03 +00:00
Mustapha Zorgati 3812e9e883
TSK-1709: optimized other mockStatic calls to match recommendation
which is to use mockStatic together with a try-with-resources statement
2021-08-19 21:47:18 +02:00
Mustapha Zorgati 9d329c5a83
TSK-1709: optimized LoggingAspect
Now it requires a system together with the log level
2021-08-19 19:04:38 +02:00
Mustapha Zorgati 4793aab361 Updated poms to version 4.7.3-SNAPSHOT 2021-08-19 09:53:55 +00:00
Mustapha Zorgati 92c374e9e4 TSK-1707: Performance optimization for TaskService#getTask and TaskQuery#list
The execution time has been reduced from O(n²) to O(n)
2021-08-19 01:35:18 +02:00
Mustapha Zorgati fb92a4df80 TSK-1708: The JointPoint for our LoggingAspect now only looks for classes within pro.taskana package 2021-08-19 01:22:17 +02:00
Mustapha Zorgati d7bc392c97 Updated poms to version 4.7.2-SNAPSHOT 2021-08-05 20:26:27 +00:00
Mustapha Zorgati e9164c6334 TSK-1691: run CI on every PR 2021-08-05 21:32:59 +02:00
Mustapha Zorgati 4812ee1801 TSK-1691: made TASKANA v4.7.X compatible with schema v4.8.0 2021-08-05 21:32:59 +02:00
39 changed files with 371 additions and 292 deletions

View File

@ -7,8 +7,7 @@ on:
tags:
- v[0-9]+\.[0-9]+\.[0-9]+
pull_request:
branches:
- master
env:
JAVA_VERSION: 11
NODE_VERSION: 14.16.0

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-common-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>taskana-common-logging</artifactId>
@ -9,9 +9,9 @@
<description>The global trace logging implementation</description>
<parent>
<artifactId>taskana-common-parent</artifactId>
<groupId>pro.taskana</groupId>
<version>4.7.1-SNAPSHOT</version>
<artifactId>taskana-common-parent</artifactId>
<version>4.7.4-SNAPSHOT</version>
</parent>
<dependencies>
@ -42,6 +42,11 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -14,59 +14,73 @@ import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@NoLogging
@Aspect
public class LoggingAspect {
public static final String ENABLE_LOGGING_ASPECT_PROPERTY_KEY = "enableLoggingAspect";
private static final Map<String, Logger> CLASS_TO_LOGGER = new ConcurrentHashMap<>();
@Pointcut(
"!@annotation(pro.taskana.common.internal.logging.NoLogging)"
+ " && execution(* *(..))"
+ " && !within(@pro.taskana.common.internal.logging.NoLogging *)"
+ " && execution(* pro.taskana..*(..))"
+ " && !execution(* lambda*(..))"
+ " && !execution(* access*(..))"
+ " && !execution(String *.toString())"
+ " && !execution(int *.hashCode())"
+ " && !execution(boolean *.canEqual(Object))"
+ " && !execution(boolean *.equals(Object))")
public void traceLogging() {}
// This method exists, so that we can mock the system property during testing.
public static String getLoggingAspectEnabledPropertyValue() {
return LazyHolder.ENABLE_LOGGING_ASPECT_PROPERTY_VALUE;
}
@Before("traceLogging()")
public void beforeMethodExecuted(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String declaringTypeName = methodSignature.getDeclaringTypeName();
Logger currentLogger =
CLASS_TO_LOGGER.computeIfAbsent(declaringTypeName, LoggerFactory::getLogger);
if ("true".equals(getLoggingAspectEnabledPropertyValue())) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String declaringTypeName = methodSignature.getDeclaringTypeName();
Logger currentLogger =
CLASS_TO_LOGGER.computeIfAbsent(declaringTypeName, LoggerFactory::getLogger);
if (currentLogger.isTraceEnabled()) {
String methodName = methodSignature.getName();
Object[] values = joinPoint.getArgs();
String[] parameterNames = methodSignature.getParameterNames();
String parametersValues = mapParametersNameValue(parameterNames, values);
if (currentLogger.isTraceEnabled()) {
String methodName = methodSignature.getName();
Object[] values = joinPoint.getArgs();
String[] parameterNames = methodSignature.getParameterNames();
String parametersValues = mapParametersNameValue(parameterNames, values);
currentLogger.trace("entry to {}({})", methodName, parametersValues);
currentLogger.trace("entry to {}({})", methodName, parametersValues);
}
}
}
@AfterReturning(pointcut = "traceLogging()", returning = "returnedObject")
public void afterMethodExecuted(JoinPoint joinPoint, Object returnedObject) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String declaringTypeName = methodSignature.getDeclaringTypeName();
Logger currentLogger =
CLASS_TO_LOGGER.computeIfAbsent(declaringTypeName, LoggerFactory::getLogger);
if ("true".equals(getLoggingAspectEnabledPropertyValue())) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String declaringTypeName = methodSignature.getDeclaringTypeName();
Logger currentLogger =
CLASS_TO_LOGGER.computeIfAbsent(declaringTypeName, LoggerFactory::getLogger);
if (currentLogger.isTraceEnabled()) {
String methodName = methodSignature.getName();
// unfortunately necessary, because this method returns a raw type
Class<?> returnType = methodSignature.getReturnType();
if (returnType.isAssignableFrom(void.class)) {
currentLogger.trace("exit from {}.", methodName);
} else {
currentLogger.trace(
"exit from {}. Returning: '{}'", methodName, Objects.toString(returnedObject, "null"));
if (currentLogger.isTraceEnabled()) {
String methodName = methodSignature.getName();
// unfortunately necessary, because this method returns a raw type
Class<?> returnType = methodSignature.getReturnType();
if (returnType.isAssignableFrom(void.class)) {
currentLogger.trace("exit from {}.", methodName);
} else {
currentLogger.trace(
"exit from {}. Returning: '{}'",
methodName,
Objects.toString(returnedObject, "null"));
}
}
}
}
@NoLogging
private static String mapParametersNameValue(String[] parameterNames, Object[] values) {
Map<String, Object> parametersNameToValue = new HashMap<>();
@ -82,4 +96,13 @@ public class LoggingAspect {
}
return stringBuilder.toString();
}
// This Initialization-on-demand holder idiom is necessary so that the retrieval of the system
// property will be executed during the execution of the first JointPoint.
// This allows us to set the system property during test execution BEFORE retrieving the system
// property.
private static class LazyHolder {
private static final String ENABLE_LOGGING_ASPECT_PROPERTY_VALUE =
System.getProperty(ENABLE_LOGGING_ASPECT_PROPERTY_KEY);
}
}

View File

@ -0,0 +1,7 @@
package outside.of.pro.staskana;
public class OutsideOfProTaskanaPackageLoggingTestClass {
@SuppressWarnings("unused")
public void doStuff() {}
}

View File

@ -0,0 +1,7 @@
package pro.taskana;
public class AtProTaskanaRootPackageLoggingTestClass {
@SuppressWarnings("unused")
public void doStuff() {}
}

View File

@ -3,80 +3,136 @@ package pro.taskana.common.internal.logging;
import static org.assertj.core.api.Assertions.assertThat;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.internal.stubbing.answers.CallsRealMethods;
import outside.of.pro.staskana.OutsideOfProTaskanaPackageLoggingTestClass;
import uk.org.lidalia.slf4jext.Level;
import uk.org.lidalia.slf4jtest.LoggingEvent;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
class LoggingAspectTest {
import pro.taskana.AtProTaskanaRootPackageLoggingTestClass;
TestLogger logger = TestLoggerFactory.getTestLogger(LoggingTestClass.class);
@NoLogging
class LoggingAspectTest {
@BeforeEach
public void clearLoggers() {
TestLoggerFactory.clear();
}
@BeforeAll
public static void setup() {
System.setProperty(LoggingAspect.ENABLE_LOGGING_ASPECT_PROPERTY_KEY, "true");
}
@Test
void should_Log_For_InternalMethod() {
void should_NotLogMethodCalls_When_ClassDoesNotResideWithinTaskanaPackage() {
OutsideOfProTaskanaPackageLoggingTestClass loggingTestClass =
new OutsideOfProTaskanaPackageLoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.doStuff();
assertThat(logger.getLoggingEvents()).isEmpty();
}
@Test
void should_LogMethod_When_ClassResidesAtTaskanaRootPackage() {
AtProTaskanaRootPackageLoggingTestClass loggingTestClass =
new AtProTaskanaRootPackageLoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.doStuff();
verifyLoggingStatement(logger, "doStuff", "", null);
}
@Test
void should_LogInternalMethod_When_ClassResidesAtTaskanaSubPackage() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.logInternalMethod();
verifyLoggingStatement("logInternalMethod", "", null);
verifyLoggingStatement(logger, "logInternalMethod", "", null);
}
@Test
void should_Log_For_InternalMethodWithReturnValue() {
void should_NotLogInternalMethod_When_SystemPropertyIsNotSet() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
try (MockedStatic<LoggingAspect> loggingAspectMockedStatic =
Mockito.mockStatic(LoggingAspect.class, new CallsRealMethods())) {
loggingAspectMockedStatic
.when(LoggingAspect::getLoggingAspectEnabledPropertyValue)
.thenReturn("");
loggingTestClass.logInternalMethod();
}
assertThat(logger.getLoggingEvents()).isEmpty();
}
@Test
void should_LogInternalMethodWithReturnValue_When_ClassResidesAtTaskanaSubPackage() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.logInternalMethodWithReturnValue();
verifyLoggingStatement("logInternalMethodWithReturnValue", "", "test string");
verifyLoggingStatement(logger, "logInternalMethodWithReturnValue", "", "test string");
}
@Test
void should_Log_For_InternalMethodWithReturnValueNull() {
void should_LogInternalMethodWithReturnValueNull_When_ClassResidesAtTaskanaSubPackage() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.logInternalMethodWithReturnValueNull();
verifyLoggingStatement("logInternalMethodWithReturnValueNull", "", "null");
verifyLoggingStatement(logger, "logInternalMethodWithReturnValueNull", "", "null");
}
@Test
void should_Log_For_InternalMethodWithArguments() {
void should_LogInternalMethodWithArguments_When_ClassResidesAtTaskanaSubPackage() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.logInternalMethodWithArguments("message");
verifyLoggingStatement("logInternalMethodWithArguments", "param = message", null);
verifyLoggingStatement(logger, "logInternalMethodWithArguments", "param = message", null);
}
@Test
void should_Log_For_InternalMethodWithReturnValueAndArguments() {
void should_LogInternalMethodWithReturnValueAndArguments_When_ClassResidesAtTaskanaSubPackage() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.logInternalMethodWithReturnValueAndArguments("message");
verifyLoggingStatement(
"logInternalMethodWithReturnValueAndArguments", "param = message", "return value");
logger, "logInternalMethodWithReturnValueAndArguments", "param = message", "return value");
}
@Test
void should_NotLog_For_ExternalMethod() {
void should_NotLogExternalMethod_When_AMethodCallsAMethodOutsideOfTaskanaPackage() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.callsExternalMethod();
verifyLoggingStatement("callsExternalMethod", "", null);
verifyLoggingStatement(logger, "callsExternalMethod", "", null);
}
@Test
void should_LogMultipleMethods_When_SubMethodIsCalled() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.logInternalMethodWrapper();
assertThat(logger.getLoggingEvents())
@ -88,6 +144,7 @@ class LoggingAspectTest {
@Test
void should_NotLogInternalMethod_When_MethodIsAnnotatedWithNoLogging() {
LoggingTestClass loggingTestClass = new LoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(loggingTestClass.getClass());
loggingTestClass.doNotLogInternalMethod();
@ -97,6 +154,7 @@ class LoggingAspectTest {
@Test
void should_NotLogInternalMethod_When_ClassIsAnnotatedWithNoLogging() {
NoLoggingTestClass noLoggingTestClass = new NoLoggingTestClass();
TestLogger logger = TestLoggerFactory.getTestLogger(noLoggingTestClass.getClass());
noLoggingTestClass.doNotLogInternalMethod();
@ -106,13 +164,15 @@ class LoggingAspectTest {
@Test
void should_NotLogInternalMethod_When_SuperClassIsAnnotatedWithNoLogging() {
NoLoggingTestSubClass noLoggingTestSubClass = new NoLoggingTestSubClass();
TestLogger logger = TestLoggerFactory.getTestLogger(noLoggingTestSubClass.getClass());
noLoggingTestSubClass.doNotLogInternalMethod();
assertThat(logger.getLoggingEvents()).isEmpty();
}
private void verifyLoggingStatement(String methodName, String arguments, Object returnValue) {
private void verifyLoggingStatement(
TestLogger logger, String methodName, String arguments, Object returnValue) {
assertThat(logger.getLoggingEvents()).hasSize(2);
LoggingEvent entryLoggingEvent = logger.getLoggingEvents().get(0);
assertThat(entryLoggingEvent.getLevel()).isEqualTo(Level.TRACE);
@ -131,47 +191,4 @@ class LoggingAspectTest {
assertThat(exitLoggingEvent.getArguments()).containsExactly(methodName, returnValue);
}
}
static class LoggingTestClass {
public void logInternalMethod() {}
@SuppressWarnings("UnusedReturnValue")
String logInternalMethodWithReturnValue() {
return "test string";
}
@SuppressWarnings("UnusedReturnValue")
public String logInternalMethodWithReturnValueNull() {
return null;
}
@SuppressWarnings("unused")
public void logInternalMethodWithArguments(String param) {}
@SuppressWarnings({"UnusedReturnValue", "unused"})
public String logInternalMethodWithReturnValueAndArguments(String param) {
return "return value";
}
public void logInternalMethodWrapper() {
logInternalMethodPrivate();
}
@SuppressWarnings("unused")
public void callsExternalMethod() {
String sum = String.valueOf(5);
}
@NoLogging
public void doNotLogInternalMethod() {}
private void logInternalMethodPrivate() {}
}
@NoLogging
static class NoLoggingTestClass {
public void doNotLogInternalMethod() {}
}
static class NoLoggingTestSubClass extends NoLoggingTestClass {}
}

View File

@ -0,0 +1,38 @@
package pro.taskana.common.internal.logging;
class LoggingTestClass {
public void logInternalMethod() {}
@SuppressWarnings("UnusedReturnValue")
public String logInternalMethodWithReturnValue() {
return "test string";
}
@SuppressWarnings("UnusedReturnValue")
public String logInternalMethodWithReturnValueNull() {
return null;
}
@SuppressWarnings("unused")
public void logInternalMethodWithArguments(String param) {}
@SuppressWarnings({"UnusedReturnValue", "unused"})
public String logInternalMethodWithReturnValueAndArguments(String param) {
return "return value";
}
public void logInternalMethodWrapper() {
logInternalMethodPrivate();
}
@SuppressWarnings("unused")
public void callsExternalMethod() {
String sum = String.valueOf(5);
}
@NoLogging
public void doNotLogInternalMethod() {}
private void logInternalMethodPrivate() {}
}

View File

@ -0,0 +1,7 @@
package pro.taskana.common.internal.logging;
@NoLogging
class NoLoggingTestClass {
public void doNotLogInternalMethod() {}
}

View File

@ -0,0 +1,3 @@
package pro.taskana.common.internal.logging;
class NoLoggingTestSubClass extends NoLoggingTestClass {}

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-common-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
</parent>
<dependencies>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-common-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-common-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
</parent>
<dependencies>

View File

@ -12,15 +12,16 @@ import pro.taskana.common.api.exceptions.SystemException;
public class SecurityVerifier {
public static final String SECURITY_FLAG_COLUMN_NAME = "ENFORCE_SECURITY";
public static final String INSERT_SECURITY_FLAG_SQL =
"INSERT INTO %s.CONFIGURATION (" + SECURITY_FLAG_COLUMN_NAME + ") VALUES (%b)";
public static final String SELECT_SECURITY_FLAG_SQL = "SELECT %s FROM %s.CONFIGURATION";
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityVerifier.class);
private static final String SECURITY_FLAG_COLUMN_NAME = "ENFORCE_SECURITY";
private static final String INSERT_SECURITY_FLAG = "INSERT INTO %s.CONFIGURATION VALUES (%b)";
private static final String SELECT_SECURITY_FLAG = "SELECT %s FROM %s.CONFIGURATION";
private final String schemaName;
private final DataSource dataSource;
public SecurityVerifier(DataSource dataSource, String schema) {
super();
this.dataSource = dataSource;
this.schemaName = schema;
}
@ -35,7 +36,7 @@ public class SecurityVerifier {
SqlRunner sqlRunner = new SqlRunner(connection);
String querySecurity =
String.format(SELECT_SECURITY_FLAG, SECURITY_FLAG_COLUMN_NAME, schemaName);
String.format(SELECT_SECURITY_FLAG_SQL, SECURITY_FLAG_COLUMN_NAME, schemaName);
if ((boolean) sqlRunner.selectOne(querySecurity).get(SECURITY_FLAG_COLUMN_NAME)
&& !securityEnabled) {
@ -65,7 +66,8 @@ public class SecurityVerifier {
try (Connection connection = dataSource.getConnection()) {
String setSecurityFlagSql = String.format(INSERT_SECURITY_FLAG, schemaName, securityEnabled);
String setSecurityFlagSql =
String.format(INSERT_SECURITY_FLAG_SQL, schemaName, securityEnabled);
try (PreparedStatement preparedStatement = connection.prepareStatement(setSecurityFlagSql)) {

View File

@ -12,7 +12,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana.history</groupId>
<artifactId>taskana-history-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana.history</groupId>
<artifactId>taskana-history-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana.history</groupId>
<artifactId>taskana-history-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-lib-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-lib-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-lib-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -1,6 +1,7 @@
package pro.taskana.task.internal;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Delete;
@ -81,7 +82,7 @@ public interface AttachmentMapper {
@Result(property = "channel", column = "CHANNEL")
@Result(property = "received", column = "RECEIVED")
List<AttachmentSummaryImpl> findAttachmentSummariesByTaskIds(
@Param("taskIds") List<String> taskIds);
@Param("taskIds") Collection<String> taskIds);
@Delete("DELETE FROM ATTACHMENT WHERE ID=#{attachmentId}")
void delete(@Param("attachmentId") String attachmentId);

View File

@ -6,13 +6,13 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -59,6 +59,7 @@ import pro.taskana.task.api.exceptions.TaskCommentNotFoundException;
import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.exceptions.UpdateFailedException;
import pro.taskana.task.api.models.Attachment;
import pro.taskana.task.api.models.AttachmentSummary;
import pro.taskana.task.api.models.ObjectReference;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskComment;
@ -274,7 +275,7 @@ public class TaskServiceImpl implements TaskService {
@Override
public Task getTask(String id) throws NotAuthorizedException, TaskNotFoundException {
TaskImpl resultTask = null;
TaskImpl resultTask;
try {
taskanaEngine.openConnection();
@ -302,18 +303,13 @@ public class TaskServiceImpl implements TaskService {
attachmentImpls = new ArrayList<>();
}
List<ClassificationSummary> classifications;
classifications = findClassificationForTaskImplAndAttachments(resultTask, attachmentImpls);
List<Attachment> attachments =
addClassificationSummariesToAttachments(attachmentImpls, classifications);
resultTask.setAttachments(attachments);
Map<String, ClassificationSummary> classificationSummariesById =
findClassificationForTaskImplAndAttachments(resultTask, attachmentImpls);
addClassificationSummariesToAttachments(attachmentImpls, classificationSummariesById);
resultTask.setAttachments(new ArrayList<>(attachmentImpls));
String classificationId = resultTask.getClassificationSummary().getId();
ClassificationSummary classification =
classifications.stream()
.filter(c -> c.getId().equals(classificationId))
.findFirst()
.orElse(null);
ClassificationSummary classification = classificationSummariesById.get(classificationId);
if (classification == null) {
throw new SystemException(
"Could not find a Classification for task " + resultTask.getId());
@ -346,7 +342,7 @@ public class TaskServiceImpl implements TaskService {
@Override
public Task setTaskRead(String taskId, boolean isRead)
throws TaskNotFoundException, NotAuthorizedException {
TaskImpl task = null;
TaskImpl task;
try {
taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId);
@ -942,8 +938,8 @@ public class TaskServiceImpl implements TaskService {
private List<TaskSummaryImpl> augmentTaskSummariesByContainedSummariesWithoutPartitioning(
List<TaskSummaryImpl> taskSummaries) {
List<String> taskIds =
taskSummaries.stream().map(TaskSummaryImpl::getId).distinct().collect(Collectors.toList());
Set<String> taskIds =
taskSummaries.stream().map(TaskSummaryImpl::getId).collect(Collectors.toSet());
if (taskIds.isEmpty()) {
taskIds = null;
@ -955,15 +951,17 @@ public class TaskServiceImpl implements TaskService {
+ "about to query for attachmentSummaries ",
taskSummaries);
}
List<AttachmentSummaryImpl> attachmentSummaries =
attachmentMapper.findAttachmentSummariesByTaskIds(taskIds);
List<ClassificationSummary> classifications =
Map<String, ClassificationSummary> classificationSummariesById =
findClassificationsForTasksAndAttachments(taskSummaries, attachmentSummaries);
Map<String, WorkbasketSummary> workbasketSummariesById = findWorkbasketsForTasks(taskSummaries);
addClassificationSummariesToTaskSummaries(taskSummaries, classifications);
addWorkbasketSummariesToTaskSummaries(taskSummaries);
addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries, classifications);
addClassificationSummariesToAttachments(attachmentSummaries, classificationSummariesById);
addClassificationSummariesToTaskSummaries(taskSummaries, classificationSummariesById);
addWorkbasketSummariesToTaskSummaries(taskSummaries, workbasketSummariesById);
addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries);
return taskSummaries;
}
@ -1522,192 +1520,159 @@ public class TaskServiceImpl implements TaskService {
}
}
private Map<String, WorkbasketSummary> findWorkbasketsForTasks(
List<? extends TaskSummary> taskSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return Collections.emptyMap();
}
Set<String> workbasketIds =
taskSummaries.stream()
.map(TaskSummary::getWorkbasketSummary)
.map(WorkbasketSummary::getId)
.collect(Collectors.toSet());
return queryWorkbasketsForTasks(workbasketIds).stream()
.collect(Collectors.toMap(WorkbasketSummary::getId, Function.identity()));
}
private Map<String, ClassificationSummary> findClassificationsForTasksAndAttachments(
List<? extends TaskSummary> taskSummaries,
List<? extends AttachmentSummaryImpl> attachmentSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return Collections.emptyMap();
}
Set<String> classificationIds =
Stream.concat(
taskSummaries.stream().map(TaskSummary::getClassificationSummary),
attachmentSummaries.stream().map(AttachmentSummary::getClassificationSummary))
.map(ClassificationSummary::getId)
.collect(Collectors.toSet());
return queryClassificationsForTasksAndAttachments(classificationIds).stream()
.collect(Collectors.toMap(ClassificationSummary::getId, Function.identity()));
}
private Map<String, ClassificationSummary> findClassificationForTaskImplAndAttachments(
TaskImpl task, List<AttachmentImpl> attachmentImpls) {
return findClassificationsForTasksAndAttachments(
Collections.singletonList(task), attachmentImpls);
}
private List<ClassificationSummary> queryClassificationsForTasksAndAttachments(
Set<String> classificationIds) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"queryClassificationsForTasksAndAttachments() about to query classifications and exit");
}
return this.classificationService
.createClassificationQuery()
.idIn(classificationIds.toArray(new String[0]))
.list();
}
private List<WorkbasketSummary> queryWorkbasketsForTasks(Set<String> workbasketIds) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("queryWorkbasketsForTasks() about to query workbaskets and exit");
}
// perform classification query
return this.workbasketService
.createWorkbasketQuery()
.idIn(workbasketIds.toArray(new String[0]))
.list();
}
private void addClassificationSummariesToTaskSummaries(
List<TaskSummaryImpl> tasks, List<ClassificationSummary> classifications) {
List<TaskSummaryImpl> tasks, Map<String, ClassificationSummary> classificationSummaryById) {
if (tasks == null || tasks.isEmpty()) {
return;
}
// assign query results to appropriate tasks.
for (TaskSummaryImpl task : tasks) {
String classificationId = task.getClassificationSummary().getId();
ClassificationSummary classificationSummary =
classifications.stream()
.filter(c -> c.getId().equals(classificationId))
.findFirst()
.orElse(null);
ClassificationSummary classificationSummary = classificationSummaryById.get(classificationId);
if (classificationSummary == null) {
throw new SystemException(
"Did not find a Classification for task (Id="
+ task.getId()
+ ",classification="
+ ",Classification="
+ task.getClassificationSummary().getId()
+ ")");
}
// set the classification on the task object
task.setClassificationSummary(classificationSummary);
}
}
private List<ClassificationSummary> findClassificationsForTasksAndAttachments(
List<TaskSummaryImpl> taskSummaries, List<AttachmentSummaryImpl> attachmentSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return new ArrayList<>();
}
Set<String> classificationIdSet =
taskSummaries.stream()
.map(t -> t.getClassificationSummary().getId())
.collect(Collectors.toSet());
if (attachmentSummaries != null && !attachmentSummaries.isEmpty()) {
for (AttachmentSummaryImpl att : attachmentSummaries) {
classificationIdSet.add(att.getClassificationSummary().getId());
}
}
return queryClassificationsForTasksAndAttachments(classificationIdSet);
}
private List<ClassificationSummary> findClassificationForTaskImplAndAttachments(
TaskImpl task, List<AttachmentImpl> attachmentImpls) {
Set<String> classificationIdSet =
new HashSet<>(Collections.singletonList(task.getClassificationSummary().getId()));
if (attachmentImpls != null && !attachmentImpls.isEmpty()) {
for (AttachmentImpl att : attachmentImpls) {
classificationIdSet.add(att.getClassificationSummary().getId());
}
}
return queryClassificationsForTasksAndAttachments(classificationIdSet);
}
private List<ClassificationSummary> queryClassificationsForTasksAndAttachments(
Set<String> classificationIdSet) {
String[] classificationIdArray = classificationIdSet.toArray(new String[0]);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"getClassificationsForTasksAndAttachments() about to query classifications and exit");
}
// perform classification query
return this.classificationService
.createClassificationQuery()
.idIn(classificationIdArray)
.list();
}
private void addWorkbasketSummariesToTaskSummaries(List<TaskSummaryImpl> taskSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
private void addWorkbasketSummariesToTaskSummaries(
List<TaskSummaryImpl> tasks, Map<String, WorkbasketSummary> workbasketSummaryById) {
if (tasks == null || tasks.isEmpty()) {
return;
}
// calculate parameters for workbasket query: workbasket keys
String[] workbasketIdArray =
taskSummaries.stream()
.map(t -> t.getWorkbasketSummary().getId())
.distinct()
.toArray(String[]::new);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("addWorkbasketSummariesToTaskSummaries() about to query workbaskets");
}
WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery();
query.setUsedToAugmentTasks(true);
List<WorkbasketSummary> workbaskets = query.idIn(workbasketIdArray).list();
Iterator<TaskSummaryImpl> taskIterator = taskSummaries.iterator();
while (taskIterator.hasNext()) {
TaskSummaryImpl task = taskIterator.next();
String workbasketId = task.getWorkbasketSummaryImpl().getId();
WorkbasketSummary workbasketSummary =
workbaskets.stream()
.filter(x -> workbasketId != null && workbasketId.equals(x.getId()))
.findFirst()
.orElse(null);
for (TaskSummaryImpl task : tasks) {
String workbasketId = task.getWorkbasketSummary().getId();
WorkbasketSummary workbasketSummary = workbasketSummaryById.get(workbasketId);
if (workbasketSummary == null) {
LOGGER.warn("Could not find a Workbasket for task {}.", task.getId());
taskIterator.remove();
continue;
throw new SystemException(
"Did not find a Workbasket for task (Id="
+ task.getId()
+ ",Workbasket="
+ task.getWorkbasketSummary().getId()
+ ")");
}
task.setWorkbasketSummary(workbasketSummary);
}
}
private void addAttachmentSummariesToTaskSummaries(
List<TaskSummaryImpl> taskSummaries,
List<AttachmentSummaryImpl> attachmentSummaries,
List<ClassificationSummary> classifications) {
List<TaskSummaryImpl> taskSummaries, List<AttachmentSummaryImpl> attachmentSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
return;
}
// augment attachment summaries by classification summaries
// Note:
// the mapper sets for each Attachment summary the property classificationSummary.key from the
// CLASSIFICATION_KEY property in the DB
addClassificationSummariesToAttachmentSummaries(
attachmentSummaries, taskSummaries, classifications);
// assign attachment summaries to task summaries
for (TaskSummaryImpl task : taskSummaries) {
for (AttachmentSummaryImpl attachment : attachmentSummaries) {
if (attachment.getTaskId() != null && attachment.getTaskId().equals(task.getId())) {
task.addAttachmentSummary(attachment);
}
Map<String, TaskSummaryImpl> taskSummariesById =
taskSummaries.stream()
.collect(
Collectors.toMap(
TaskSummary::getId,
Function.identity(),
// Currently, we still have a bug (TSK-1204), where the TaskQuery#list function
// returns the same task multiple times when that task has more than one
// attachment...Therefore, this MergeFunction is necessary.
(a, b) -> b));
for (AttachmentSummaryImpl attachmentSummary : attachmentSummaries) {
String taskId = attachmentSummary.getTaskId();
TaskSummaryImpl taskSummary = taskSummariesById.get(taskId);
if (taskSummary != null) {
taskSummary.addAttachmentSummary(attachmentSummary);
}
}
}
private void addClassificationSummariesToAttachmentSummaries(
List<AttachmentSummaryImpl> attachmentSummaries,
List<TaskSummaryImpl> taskSummaries,
List<ClassificationSummary> classifications) {
// prereq: in each attachmentSummary, the classificationSummary.key property is set.
if (attachmentSummaries == null
|| attachmentSummaries.isEmpty()
|| taskSummaries == null
|| taskSummaries.isEmpty()) {
private void addClassificationSummariesToAttachments(
List<? extends AttachmentSummaryImpl> attachments,
Map<String, ClassificationSummary> classificationSummariesById) {
if (attachments == null || attachments.isEmpty()) {
return;
}
// iterate over all attachment summaries an add the appropriate classification summary to each
for (AttachmentSummaryImpl att : attachmentSummaries) {
String classificationId = att.getClassificationSummary().getId();
for (AttachmentSummaryImpl attachment : attachments) {
String classificationId = attachment.getClassificationSummary().getId();
ClassificationSummary classificationSummary =
classifications.stream()
.filter(x -> classificationId != null && classificationId.equals(x.getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + att);
}
att.setClassificationSummary(classificationSummary);
}
}
private List<Attachment> addClassificationSummariesToAttachments(
List<AttachmentImpl> attachmentImpls, List<ClassificationSummary> classifications) {
if (attachmentImpls == null || attachmentImpls.isEmpty()) {
return new ArrayList<>();
}
List<Attachment> result = new ArrayList<>();
for (AttachmentImpl att : attachmentImpls) {
// find the associated task to use the correct domain
ClassificationSummary classificationSummary =
classifications.stream()
.filter(c -> c != null && c.getId().equals(att.getClassificationSummary().getId()))
.findFirst()
.orElse(null);
classificationSummariesById.get(classificationId);
if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + att);
throw new SystemException("Could not find a Classification for attachment " + attachment);
}
att.setClassificationSummary(classificationSummary);
result.add(att);
attachment.setClassificationSummary(classificationSummary);
}
return result;
}
private TaskImpl initUpdatedTask(

View File

@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test;
import pro.taskana.TaskanaEngineConfiguration;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.configuration.DbSchemaCreator;
import pro.taskana.common.internal.configuration.SecurityVerifier;
import pro.taskana.sampledata.SampleDataGenerator;
class TaskanaSecurityConfigAccTest {
@ -88,7 +89,9 @@ class TaskanaSecurityConfigAccTest {
String selectSecurityFlagSql =
String.format(
"SELECT * FROM %s.CONFIGURATION", TaskanaEngineTestConfiguration.getSchemaName());
SecurityVerifier.SELECT_SECURITY_FLAG_SQL,
SecurityVerifier.SECURITY_FLAG_COLUMN_NAME,
TaskanaEngineTestConfiguration.getSchemaName());
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(selectSecurityFlagSql);
@ -107,8 +110,9 @@ class TaskanaSecurityConfigAccTest {
String sql =
String.format(
"INSERT INTO %s.CONFIGURATION VALUES (%b)",
TaskanaEngineTestConfiguration.getSchemaName(), securityFlag);
SecurityVerifier.INSERT_SECURITY_FLAG_SQL,
TaskanaEngineTestConfiguration.getSchemaName(),
securityFlag);
Statement statement = connection.createStatement();
statement.execute(sql);

View File

@ -136,14 +136,15 @@ class QueryTasksAccTest extends AbstractAccTest {
@WithAccessId(user = "admin")
@Test
void should_SplitTaskListIntoChunksOf32000_When_AugmentingTasksAfterTaskQuery() {
MockedStatic<CollectionUtil> listUtilMock = Mockito.mockStatic(CollectionUtil.class);
listUtilMock
.when(() -> CollectionUtil.partitionBasedOnSize(any(), anyInt()))
.thenCallRealMethod();
try (MockedStatic<CollectionUtil> listUtilMock = Mockito.mockStatic(CollectionUtil.class)) {
listUtilMock
.when(() -> CollectionUtil.partitionBasedOnSize(any(), anyInt()))
.thenCallRealMethod();
TASK_SERVICE.createTaskQuery().list();
TASK_SERVICE.createTaskQuery().list();
listUtilMock.verify(() -> CollectionUtil.partitionBasedOnSize(any(), eq(32000)));
listUtilMock.verify(() -> CollectionUtil.partitionBasedOnSize(any(), eq(32000)));
}
}
@WithAccessId(user = "admin")

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-lib-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-lib-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>${project.groupId}:${project.artifactId}</name>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-routing-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>4.7.1-SNAPSHOT</version>
<version>4.7.4-SNAPSHOT</version>
<relativePath>../rest/pom.xml</relativePath>
</parent>