TSK-1647: Implemented an error key for every exception

Co-authored-by: Tristan Eisermann<19949441+Tristan2357@users.noreply.github.com>
Co-authored-by: Tim Gerversmann<72377965+tge20@users.noreply.github.com>
Co-authored-by: Sofie Hofmann<29145005+sofie29@users.noreply.github.com>

javaDoc Exceptions

blub

TSK-1647: now validating existing & not authorized tasks :)
This commit is contained in:
Mustapha Zorgati 2021-07-08 03:45:15 +02:00
parent 207d291787
commit 34d2bbfa92
118 changed files with 2239 additions and 1274 deletions

View File

@ -135,11 +135,6 @@ class LoggingAspectTest {
static class LoggingTestClass { static class LoggingTestClass {
public void logInternalMethod() {} public void logInternalMethod() {}
@SuppressWarnings("UnusedReturnValue")
String logInternalMethodWithReturnValue() {
return "test string";
}
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
public String logInternalMethodWithReturnValueNull() { public String logInternalMethodWithReturnValueNull() {
return null; return null;
@ -165,6 +160,11 @@ class LoggingAspectTest {
@NoLogging @NoLogging
public void doNotLogInternalMethod() {} public void doNotLogInternalMethod() {}
@SuppressWarnings("UnusedReturnValue")
String logInternalMethodWithReturnValue() {
return "test string";
}
private void logInternalMethodPrivate() {} private void logInternalMethodPrivate() {}
} }

View File

@ -19,7 +19,7 @@ public class BulkOperationResults<K, V extends Exception> {
/** /**
* Returning a list of current errors as map. If there are no errors the result will be empty. * Returning a list of current errors as map. If there are no errors the result will be empty.
* *
* @return map of errors which can´t be null. * @return map of errors which can't be null.
*/ */
public Map<K, V> getErrorMap() { public Map<K, V> getErrorMap() {
return this.errorMap; return this.errorMap;
@ -82,7 +82,7 @@ public class BulkOperationResults<K, V extends Exception> {
/** /**
* Map from any exception type to Exception. * Map from any exception type to Exception.
* *
* @return map of errors which can´t be null. * @return map of errors which can't be null.
*/ */
public BulkOperationResults<K, Exception> mapBulkOperationResults() { public BulkOperationResults<K, Exception> mapBulkOperationResults() {
BulkOperationResults<K, Exception> bulkLogMapped = new BulkOperationResults<>(); BulkOperationResults<K, Exception> bulkLogMapped = new BulkOperationResults<>();

View File

@ -1,9 +1,13 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** Thrown in ConnectionManagementMode AUTOCOMMIT when an attempt to commit fails. */ /**
* This exception is thrown in ConnectionManagementMode AUTOCOMMIT when an attempt to commit fails.
*/
public class AutocommitFailedException extends TaskanaRuntimeException { public class AutocommitFailedException extends TaskanaRuntimeException {
public static final String ERROR_KEY = "CONNECTION_AUTOCOMMIT_FAILED";
public AutocommitFailedException(Throwable cause) { public AutocommitFailedException(Throwable cause) {
super("Autocommit failed", cause); super("Autocommit failed", ErrorCode.of(ERROR_KEY), cause);
} }
} }

View File

@ -6,7 +6,11 @@ package pro.taskana.common.api.exceptions;
*/ */
public class ConcurrencyException extends TaskanaException { public class ConcurrencyException extends TaskanaException {
public ConcurrencyException(String msg) { public static final String ERROR_KEY = "ENTITY_NOT_UP_TO_DATE";
super(msg);
public ConcurrencyException() {
super(
"The current entity cannot be updated since it has been modified while editing.",
ErrorCode.of(ERROR_KEY));
} }
} }

View File

@ -1,12 +1,14 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** /**
* Thrown if ConnectionManagementMode is CONNECTION_MANAGED_EXTERNALLY and an attempt is made to * This exception is thrown when ConnectionManagementMode is CONNECTION_MANAGED_EXTERNALLY and an
* call an API method before the setConnection() method has been called. * attempt is made to call an API method before the setConnection() method has been called.
*/ */
public class ConnectionNotSetException extends TaskanaRuntimeException { public class ConnectionNotSetException extends TaskanaRuntimeException {
public static final String ERROR_KEY = "CONNECTION_NOT_SET";
public ConnectionNotSetException() { public ConnectionNotSetException() {
super("Connection not set"); super("Connection not set", ErrorCode.of(ERROR_KEY));
} }
} }

View File

@ -1,11 +1,21 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** import pro.taskana.common.internal.util.MapCreator;
* This exception is thrown if a domain name is specified which is not found in the configuration.
*/ /** This exception is thrown when the specified domain is not found in the configuration. */
public class DomainNotFoundException extends NotFoundException { public class DomainNotFoundException extends NotFoundException {
public DomainNotFoundException(String domain, String msg) { public static final String ERROR_KEY = "DOMAIN_NOT_FOUND";
super(domain, msg); private final String domain;
public DomainNotFoundException(String domain) {
super(
String.format("Domain '%s' does not exist in the configuration", domain),
ErrorCode.of(ERROR_KEY, MapCreator.of("domain", domain)));
this.domain = domain;
}
public String getDomain() {
return domain;
} }
} }

View File

@ -0,0 +1,57 @@
package pro.taskana.common.api.exceptions;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ErrorCode implements Serializable {
private final String key;
// Unfortunately this is necessary. The Map interface does not implement Serializable..
private final HashMap<String, Serializable> messageVariables;
private ErrorCode(String key, Map<String, Serializable> messageVariables) {
this.key = key;
this.messageVariables = new HashMap<>(messageVariables);
}
public static ErrorCode of(String key, Map<String, Serializable> messageVariables) {
return new ErrorCode(key, messageVariables);
}
public static ErrorCode of(String key) {
return new ErrorCode(key, Collections.emptyMap());
}
public String getKey() {
return key;
}
public Map<String, Serializable> getMessageVariables() {
return messageVariables;
}
@Override
public int hashCode() {
return Objects.hash(key, messageVariables);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ErrorCode)) {
return false;
}
ErrorCode other = (ErrorCode) obj;
return Objects.equals(key, other.key)
&& Objects.equals(messageVariables, other.messageVariables);
}
@Override
public String toString() {
return "ErrorCode [key=" + key + ", messageVariables=" + messageVariables + "]";
}
}

View File

@ -1,13 +1,13 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** This exception is thrown when a method is called with invalid argument. */ /** This exception is thrown when a method is called with an invalid argument. */
public class InvalidArgumentException extends TaskanaException { public class InvalidArgumentException extends TaskanaException {
public InvalidArgumentException(String msg) { public InvalidArgumentException(String msg) {
super(msg); this(msg, null);
} }
public InvalidArgumentException(String msg, Throwable cause) { public InvalidArgumentException(String msg, Throwable cause) {
super(msg, cause); super(msg, ErrorCode.of("INVALID_ARGUMENT"), cause);
} }
} }

View File

@ -0,0 +1,36 @@
package pro.taskana.common.api.exceptions;
import java.util.Arrays;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.internal.util.MapCreator;
/**
* This exception is thrown when the current user is not in a certain {@linkplain TaskanaRole role}
* it is supposed to be.
*/
public class MismatchedRoleException extends NotAuthorizedException {
public static final String ERROR_KEY = "ROLE_MISMATCHED";
private final String currentUserId;
private final TaskanaRole[] roles;
public MismatchedRoleException(String currentUserId, TaskanaRole... 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, MapCreator.of("roles", roles, "currentUserId", currentUserId)));
this.currentUserId = currentUserId;
this.roles = roles;
}
public TaskanaRole[] getRoles() {
return roles;
}
public String getCurrentUserId() {
return currentUserId;
}
}

View File

@ -1,16 +1,8 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** This exception is used to communicate a not authorized user. */ /** This exception is thrown when a user is not authorized. */
public class NotAuthorizedException extends TaskanaException { public class NotAuthorizedException extends TaskanaException {
protected NotAuthorizedException(String msg, ErrorCode errorCode) {
private final String currentUserId; super(msg, errorCode);
public NotAuthorizedException(String msg, String currentUserId) {
super(msg + " - [CURRENT USER: {'" + currentUserId + "'}]");
this.currentUserId = currentUserId;
}
public String getCurrentUserId() {
return currentUserId;
} }
} }

View File

@ -1,16 +1,9 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** This exception will be thrown if a specific object is not in the database. */ /** This exception is thrown when a specific object is not in the database. */
public class NotFoundException extends TaskanaException { public class NotFoundException extends TaskanaException {
private final String id; protected NotFoundException(String message, ErrorCode errorCode) {
super(message, errorCode);
public NotFoundException(String id, String message) {
super(message);
this.id = id;
}
public String getId() {
return id;
} }
} }

View File

@ -1,13 +1,15 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** This exception is thrown when a generic taskana problem is encountered. */ /** This exception is thrown when a generic TASKANA problem is encountered. */
public class SystemException extends TaskanaRuntimeException { public class SystemException extends TaskanaRuntimeException {
public static final String ERROR_KEY = "CRITICAL_SYSTEM_ERROR";
public SystemException(String msg) { public SystemException(String msg) {
super(msg); this(msg, null);
} }
public SystemException(String msg, Throwable cause) { public SystemException(String msg, Throwable cause) {
super(msg, cause); super(msg, ErrorCode.of(ERROR_KEY), cause);
} }
} }

View File

@ -3,24 +3,18 @@ package pro.taskana.common.api.exceptions;
/** common base class for TASKANA's checked exceptions. */ /** common base class for TASKANA's checked exceptions. */
public class TaskanaException extends Exception { public class TaskanaException extends Exception {
public TaskanaException() { private final ErrorCode errorCode;
super();
protected TaskanaException(String message, ErrorCode errorCode) {
this(message, errorCode, null);
} }
public TaskanaException(String message) { protected TaskanaException(String message, ErrorCode errorCode, Throwable cause) {
super(message);
}
public TaskanaException(Throwable cause) {
super(cause);
}
public TaskanaException(String message, Throwable cause) {
super(message, cause); super(message, cause);
this.errorCode = errorCode;
} }
public TaskanaException( public ErrorCode getErrorCode() {
String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { return errorCode;
super(message, cause, enableSuppression, writableStackTrace);
} }
} }

View File

@ -1,26 +1,25 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** Common base class for Taskana's runtime exceptions. */ /** The common base class for TASKANA's runtime exceptions. */
public class TaskanaRuntimeException extends RuntimeException { public class TaskanaRuntimeException extends RuntimeException {
public TaskanaRuntimeException() { private final ErrorCode errorCode;
super();
protected TaskanaRuntimeException(String message, ErrorCode errorCode) {
this(message, errorCode, null);
} }
public TaskanaRuntimeException(String message) { protected TaskanaRuntimeException(String message, ErrorCode errorCode, Throwable cause) {
super(message);
}
public TaskanaRuntimeException(Throwable cause) {
super(cause);
}
public TaskanaRuntimeException(String message, Throwable cause) {
super(message, cause); super(message, cause);
this.errorCode = errorCode;
} }
public TaskanaRuntimeException( public ErrorCode getErrorCode() {
String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { return errorCode;
super(message, cause, enableSuppression, writableStackTrace); }
@Override
public String toString() {
return "TaskanaRuntimeException [errorCode=" + errorCode + "]";
} }
} }

View File

@ -1,11 +1,23 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
/** import pro.taskana.common.internal.util.MapCreator;
* This exception will be thrown if the database name doesn't match to one of the desired databases.
*/
public class UnsupportedDatabaseException extends RuntimeException {
public UnsupportedDatabaseException(String name) { /**
super("Database with '" + name + "' not found"); * This exception is thrown when the database name doesn't match to one of the desired databases.
*/
public class UnsupportedDatabaseException extends TaskanaRuntimeException {
public static final String ERROR_KEY = "DATABASE_UNSUPPORTED";
private final String databaseProductName;
public UnsupportedDatabaseException(String databaseProductName) {
super(
String.format("Database '%s' is not supported", databaseProductName),
ErrorCode.of(ERROR_KEY, MapCreator.of("databaseProductName", databaseProductName)));
this.databaseProductName = databaseProductName;
}
public String getDatabaseProductName() {
return databaseProductName;
} }
} }

View File

@ -1,8 +1,25 @@
package pro.taskana.common.api.exceptions; package pro.taskana.common.api.exceptions;
import pro.taskana.common.api.CustomHoliday;
import pro.taskana.common.internal.util.MapCreator;
/** This exception is thrown when an entry for the {@linkplain CustomHoliday} has a wrong format. */
public class WrongCustomHolidayFormatException extends TaskanaException { public class WrongCustomHolidayFormatException extends TaskanaException {
public WrongCustomHolidayFormatException(String message) { public static final String ERROR_KEY = "CUSTOM_HOLIDAY_WRONG_FORMAT";
super(message); private final String customHoliday;
public WrongCustomHolidayFormatException(String customHoliday) {
super(
String.format(
"Wrong format for custom holiday entry '%s'! The format should be 'dd.MM' "
+ "i.e. 01.05 for the first of May.",
customHoliday),
ErrorCode.of(ERROR_KEY, MapCreator.of("customHoliday", customHoliday)));
this.customHoliday = customHoliday;
}
public String getCustomHoliday() {
return customHoliday;
} }
} }

View File

@ -96,8 +96,8 @@ public class DbSchemaCreator {
Map<String, Object> queryResult = runner.selectOne(query); Map<String, Object> queryResult = runner.selectOne(query);
ComparableVersion actualVersion = new ComparableVersion((String) queryResult.get("VERSION")); ComparableVersion actualVersion = ComparableVersion.of((String) queryResult.get("VERSION"));
ComparableVersion minVersion = new ComparableVersion(expectedMinVersion); ComparableVersion minVersion = ComparableVersion.of(expectedMinVersion);
if (actualVersion.compareTo(minVersion) < 0) { if (actualVersion.compareTo(minVersion) < 0) {
LOGGER.error( LOGGER.error(

View File

@ -75,10 +75,14 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
private ListItem items; private ListItem items;
public ComparableVersion(String version) { private ComparableVersion(String version) {
parseVersion(version); parseVersion(version);
} }
public static ComparableVersion of(String version) {
return new ComparableVersion(version);
}
public final void parseVersion(String version) { public final void parseVersion(String version) {
this.value = version; this.value = version;
@ -226,7 +230,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
this.value = 0; this.value = 0;
} }
IntItem(String str) { private IntItem(String str) {
this.value = Integer.parseInt(str); this.value = Integer.parseInt(str);
} }
@ -294,7 +298,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
private static class LongItem implements Item { private static class LongItem implements Item {
private final long value; private final long value;
LongItem(String str) { private LongItem(String str) {
this.value = Long.parseLong(str); this.value = Long.parseLong(str);
} }
@ -363,7 +367,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
private static class BigIntegerItem implements Item { private static class BigIntegerItem implements Item {
private final BigInteger value; private final BigInteger value;
BigIntegerItem(String str) { private BigIntegerItem(String str) {
this.value = new BigInteger(str); this.value = new BigInteger(str);
} }
@ -447,7 +451,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
private final String value; private final String value;
StringItem(String value, boolean followedByDigit) { private StringItem(String value, boolean followedByDigit) {
if (followedByDigit && value.length() == 1) { if (followedByDigit && value.length() == 1) {
// a1 = alpha-1, b1 = beta-1, m1 = milestone-1 // a1 = alpha-1, b1 = beta-1, m1 = milestone-1
switch (value.charAt(0)) { switch (value.charAt(0)) {
@ -549,6 +553,9 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
* sub-lists (which start with '-(number)' in the version specification). * sub-lists (which start with '-(number)' in the version specification).
*/ */
private static class ListItem extends ArrayList<Item> implements Item { private static class ListItem extends ArrayList<Item> implements Item {
private ListItem() {}
@Override @Override
public int getType() { public int getType() {
return LIST_ITEM; return LIST_ITEM;

View File

@ -0,0 +1,22 @@
package pro.taskana.common.internal.util;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.EnumSet;
public class EnumUtil {
private EnumUtil() {
throw new IllegalStateException("Utility class");
}
@SafeVarargs
public static <E extends Enum<E>> E[] allValuesExceptFor(E... values) {
if (values == null || values.length == 0) {
throw new IllegalArgumentException("values must be present");
}
@SuppressWarnings("unchecked")
E[] array = (E[]) Array.newInstance(values[0].getClass(), 0);
return EnumSet.complementOf(EnumSet.copyOf(Arrays.asList(values))).toArray(array);
}
}

View File

@ -0,0 +1,57 @@
package pro.taskana.common.internal.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/* This class will be removed once we fully migrate from JDK8*/
public class MapCreator {
private MapCreator() {
throw new IllegalStateException("Utility class");
}
public static <K, V> Map<K, V> of() {
return Collections.emptyMap();
}
public static <K, V> Map<K, V> of(K k1, V v1) {
Map<K, V> map = new HashMap<>();
map.put(k1, v1);
return map;
}
public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {
Map<K, V> map = new HashMap<>();
map.put(k1, v1);
map.put(k2, v2);
return map;
}
public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
Map<K, V> map = new HashMap<>();
map.put(k1, v1);
map.put(k2, v2);
map.put(k3, v3);
return map;
}
public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
Map<K, V> map = new HashMap<>();
map.put(k1, v1);
map.put(k2, v2);
map.put(k3, v3);
map.put(k4, v4);
return map;
}
public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
Map<K, V> map = new HashMap<>();
map.put(k1, v1);
map.put(k2, v2);
map.put(k3, v3);
map.put(k4, v4);
map.put(k5, v5);
return map;
}
}

View File

@ -15,7 +15,9 @@ import pro.taskana.common.api.exceptions.SystemException;
public class ObjectAttributeChangeDetector { public class ObjectAttributeChangeDetector {
private ObjectAttributeChangeDetector() {} private ObjectAttributeChangeDetector() {
throw new IllegalStateException("Utility class");
}
/** /**
* Determines changes in fields between two objects. * Determines changes in fields between two objects.

View File

@ -90,7 +90,6 @@ public class LogfileHistoryServiceImpl implements TaskanaHistory {
@Override @Override
public void deleteHistoryEventsByTaskIds(List<String> taskIds) { public void deleteHistoryEventsByTaskIds(List<String> taskIds) {
throw new UnsupportedOperationException("HistoryLogger is not supposed to delete events"); throw new UnsupportedOperationException("HistoryLogger is not supposed to delete events");
} }

View File

@ -108,9 +108,7 @@ public class SimpleHistoryServiceImpl implements TaskanaHistory {
try { try {
taskanaHistoryEngine.openConnection(); taskanaHistoryEngine.openConnection();
taskHistoryEventMapper.deleteMultipleByTaskIds(taskIds); taskHistoryEventMapper.deleteMultipleByTaskIds(taskIds);
} catch (SQLException e) { } catch (SQLException e) {
LOGGER.error("Caught exception while trying to delete history events", e); LOGGER.error("Caught exception while trying to delete history events", e);
} finally { } finally {
@ -126,9 +124,7 @@ public class SimpleHistoryServiceImpl implements TaskanaHistory {
resultEvent = taskHistoryEventMapper.findById(historyEventId); resultEvent = taskHistoryEventMapper.findById(historyEventId);
if (resultEvent == null) { if (resultEvent == null) {
throw new TaskanaHistoryEventNotFoundException( throw new TaskanaHistoryEventNotFoundException(historyEventId);
historyEventId,
String.format("TaskHistoryEvent for id %s was not found", historyEventId));
} }
return resultEvent; return resultEvent;

View File

@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory;
import pro.taskana.TaskanaEngineConfiguration; import pro.taskana.TaskanaEngineConfiguration;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.TaskanaRole; import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.exceptions.MismatchedRoleException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.persistence.InstantTypeHandler; import pro.taskana.common.internal.persistence.InstantTypeHandler;
import pro.taskana.common.internal.persistence.MapTypeHandler; import pro.taskana.common.internal.persistence.MapTypeHandler;
@ -91,9 +92,7 @@ public class TaskanaHistoryEngineImpl implements TaskanaHistoryEngine {
taskanaEngine.getCurrentUserContext().getAccessIds(), taskanaEngine.getCurrentUserContext().getAccessIds(),
Arrays.toString(roles)); Arrays.toString(roles));
} }
throw new NotAuthorizedException( throw new MismatchedRoleException(taskanaEngine.getCurrentUserContext().getUserid(), roles);
"current user is not member of role(s) " + Arrays.toString(roles),
taskanaEngine.getCurrentUserContext().getUserid());
} }
} }

View File

@ -135,7 +135,7 @@ public class HistoryCleanupJob extends AbstractTaskanaJob {
LOGGER.info( LOGGER.info(
"Job ended successfully. {} history events deleted.", totalNumberOfHistoryEventsDeleted); "Job ended successfully. {} history events deleted.", totalNumberOfHistoryEventsDeleted);
} catch (Exception e) { } catch (Exception e) {
throw new TaskanaException("Error while processing HistoryCleanupJob.", e); throw new SystemException("Error while processing HistoryCleanupJob.", e);
} finally { } finally {
scheduleNextCleanupJob(); scheduleNextCleanupJob();
} }

View File

@ -20,6 +20,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import pro.taskana.common.rest.models.PageMetadata; import pro.taskana.common.rest.models.PageMetadata;
@ -270,11 +271,11 @@ class TaskHistoryEventControllerIntTest {
TASK_HISTORY_EVENT_PAGED_REPRESENTATION_MODEL_TYPE); TASK_HISTORY_EVENT_PAGED_REPRESENTATION_MODEL_TYPE);
assertThatThrownBy(httpCall) assertThatThrownBy(httpCall)
.isInstanceOf(HttpClientErrorException.class) .isInstanceOf(HttpServerErrorException.class)
.hasMessageContaining( .hasMessageContaining(
"Unkown request parameters found: [anotherIllegalParam, illegalParam]") "Unkown request parameters found: [anotherIllegalParam, illegalParam]")
.extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) .extracting(ex -> ((HttpServerErrorException) ex).getStatusCode())
.isEqualTo(HttpStatus.BAD_REQUEST); .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
} }
// endregion // endregion

View File

@ -551,11 +551,7 @@ public class TaskanaEngineConfiguration {
if (parts.size() == 2) { if (parts.size() == 2) {
return CustomHoliday.of(Integer.valueOf(parts.get(0)), Integer.valueOf(parts.get(1))); return CustomHoliday.of(Integer.valueOf(parts.get(0)), Integer.valueOf(parts.get(1)));
} }
throw new WrongCustomHolidayFormatException( throw new WrongCustomHolidayFormatException(customHolidayEntry);
String.format(
"Wrong format for custom holiday entry %s! The format should be 'dd.MM' "
+ "i.e. 01.05 for the first of may. The value will be ignored!",
customHolidayEntry));
} }
private List<String> splitStringAndTrimElements(String str, String separator) { private List<String> splitStringAndTrimElements(String str, String separator) {

View File

@ -3,6 +3,7 @@ package pro.taskana.classification.api;
import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException; import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException;
import pro.taskana.classification.api.exceptions.ClassificationInUseException; import pro.taskana.classification.api.exceptions.ClassificationInUseException;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException; import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.classification.api.exceptions.MalformedServiceLevelException;
import pro.taskana.classification.api.models.Classification; import pro.taskana.classification.api.models.Classification;
import pro.taskana.common.api.exceptions.ConcurrencyException; import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.DomainNotFoundException; import pro.taskana.common.api.exceptions.DomainNotFoundException;
@ -87,28 +88,30 @@ public interface ClassificationService {
* @throws NotAuthorizedException if the current user is not member of role BUSINESS_ADMIN or * @throws NotAuthorizedException if the current user is not member of role BUSINESS_ADMIN or
* ADMIN * ADMIN
* @throws DomainNotFoundException if the {@code domain} does not exist in the configuration * @throws DomainNotFoundException if the {@code domain} does not exist in the configuration
* @throws InvalidArgumentException if the {@code serviceLevel} property does not comply with the * @throws MalformedServiceLevelException if the {@code serviceLevel} property does not comply
* ISO 8601 specification * with the ISO 8601 specification
* @throws InvalidArgumentException if the {@linkplain Classification} contains invalid properties
*/ */
Classification createClassification(Classification classification) Classification createClassification(Classification classification)
throws ClassificationAlreadyExistException, NotAuthorizedException, DomainNotFoundException, throws ClassificationAlreadyExistException, NotAuthorizedException, DomainNotFoundException,
InvalidArgumentException; InvalidArgumentException, MalformedServiceLevelException;
/** /**
* Updates a Classification. * Updates a Classification.
* *
* @param classification the Classification to update * @param classification the Classification to update
* @return the updated Classification. * @return the updated Classification.
* @throws ClassificationNotFoundException if the classification OR it´s parent does not exist. * @throws ClassificationNotFoundException if the classification OR it's parent does not exist.
* @throws NotAuthorizedException if the caller got no ADMIN or BUSINESS_ADMIN permissions. * @throws NotAuthorizedException if the caller got no ADMIN or BUSINESS_ADMIN permissions.
* @throws ConcurrencyException If the classification was modified in the meantime and is not the * @throws ConcurrencyException If the classification was modified in the meantime and is not the
* most up to date anymore. * most up to date anymore.
* @throws InvalidArgumentException if the ServiceLevel property does not comply with the ISO 8601 * @throws MalformedServiceLevelException if the {@code serviceLevel} property does not comply
* specification * with the ISO 8601 specification
* @throws InvalidArgumentException if the {@linkplain Classification} contains invalid properties
*/ */
Classification updateClassification(Classification classification) Classification updateClassification(Classification classification)
throws ClassificationNotFoundException, NotAuthorizedException, ConcurrencyException, throws ClassificationNotFoundException, NotAuthorizedException, ConcurrencyException,
InvalidArgumentException; InvalidArgumentException, MalformedServiceLevelException;
/** /**
* This method provides a query builder for querying the database. * This method provides a query builder for querying the database.

View File

@ -1,17 +1,38 @@
package pro.taskana.classification.api.exceptions; package pro.taskana.classification.api.exceptions;
import pro.taskana.classification.api.models.Classification; import pro.taskana.classification.api.models.Classification;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
/** Thrown, when a classification does already exits, but wanted to create with same ID+domain. */ /**
* This exception is thrown when a {@linkplain Classification} does already exits, but was tried to
* be created with the same {@linkplain Classification#getId() id} and {@linkplain
* Classification#getDomain() domain}.
*/
public class ClassificationAlreadyExistException extends TaskanaException { public class ClassificationAlreadyExistException extends TaskanaException {
public static final String ERROR_KEY = "CLASSIFICATION_ALREADY_EXISTS";
private final String domain;
private final String classificationKey;
public ClassificationAlreadyExistException(Classification classification) { public ClassificationAlreadyExistException(Classification classification) {
this(classification.getKey(), classification.getDomain());
}
public ClassificationAlreadyExistException(String key, String domain) {
super( super(
"A classification with key '" String.format("A Classification with key '%s' already exists in domain '%s'.", key, domain),
+ classification.getKey() ErrorCode.of(ERROR_KEY, MapCreator.of("classificationKey", key, "domain", domain)));
+ "' already exists in domain '" classificationKey = key;
+ classification.getDomain() this.domain = domain;
+ "'."); }
public String getDomain() {
return domain;
}
public String getClassificationKey() {
return classificationKey;
} }
} }

View File

@ -1,15 +1,48 @@
package pro.taskana.classification.api.exceptions; package pro.taskana.classification.api.exceptions;
import pro.taskana.classification.api.models.Classification;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.Attachment;
import pro.taskana.task.api.models.Task;
/** Thrown if a specific task is not in the database. */ /**
* This exception is thrown when a specific {@linkplain Classification} was tried to be deleted
* while still being in use. <br>
* This could mean that there are either {@linkplain Task Tasks} or {@linkplain Attachment
* Attachments} associated with it.
*/
public class ClassificationInUseException extends TaskanaException { public class ClassificationInUseException extends TaskanaException {
public ClassificationInUseException(String msg) { public static final String ERROR_KEY = "CLASSIFICATION_IN_USE";
super(msg); private final String classificationKey;
private final String domain;
public ClassificationInUseException(Classification classification, Throwable cause) {
super(
String.format(
"The Classification with id = '%s' and key = '%s' in domain = '%s' "
+ "is in use and cannot be deleted. There are either Tasks or "
+ "Attachments associated with the Classification.",
classification.getId(), classification.getKey(), classification.getDomain()),
ErrorCode.of(
ERROR_KEY,
MapCreator.of(
"classificationKey",
classification.getKey(),
"domain",
classification.getDomain())),
cause);
classificationKey = classification.getKey();
domain = classification.getDomain();
} }
public ClassificationInUseException(String msg, Throwable cause) { public String getClassificationKey() {
super(msg, cause); return classificationKey;
}
public String getDomain() {
return domain;
} }
} }

View File

@ -1,28 +1,48 @@
package pro.taskana.classification.api.exceptions; package pro.taskana.classification.api.exceptions;
import pro.taskana.classification.api.models.Classification;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotFoundException; import pro.taskana.common.api.exceptions.NotFoundException;
import pro.taskana.common.internal.util.MapCreator;
/** Thrown if a specific task is not in the database. */ /** Thrown if a specific {@linkplain Classification} is not in the database. */
public class ClassificationNotFoundException extends NotFoundException { public class ClassificationNotFoundException extends NotFoundException {
private String key; public static final String ERROR_KEY_ID = "CLASSIFICATION_WITH_ID_NOT_FOUND";
private String domain; public static final String ERROR_KEY_KEY_DOMAIN = "CLASSIFICATION_WITH_KEY_NOT_FOUND";
private final String classificationId;
private final String classificationKey;
private final String domain;
public ClassificationNotFoundException(String id, String msg) { public ClassificationNotFoundException(String classificationId) {
super(id, msg); super(
String.format("Classification with id '%s' wasn't found", classificationId),
ErrorCode.of(ERROR_KEY_ID, MapCreator.of("classificationId", classificationId)));
this.classificationId = classificationId;
classificationKey = null;
domain = null;
} }
public ClassificationNotFoundException(String key, String domain, String msg) { public ClassificationNotFoundException(String key, String domain) {
super(null, msg); super(
this.key = key; String.format(
"Classification with key '%s' and domain '%s' could not be found", key, domain),
ErrorCode.of(
ERROR_KEY_KEY_DOMAIN, MapCreator.of("classificationKey", key, "domain", domain)));
this.classificationKey = key;
this.domain = domain; this.domain = domain;
classificationId = null;
} }
public String getKey() { public String getClassificationKey() {
return key; return classificationKey;
} }
public String getDomain() { public String getDomain() {
return domain; return domain;
} }
public String getClassificationId() {
return classificationId;
}
} }

View File

@ -0,0 +1,56 @@
package pro.taskana.classification.api.exceptions;
import pro.taskana.classification.api.models.Classification;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
/**
* This exception is thrown when the {@linkplain Classification#getServiceLevel() service level} of
* the {@linkplain Classification} has not the required format. The {@linkplain
* Classification#getServiceLevel() service level} has to be a positive ISO-8601 duration format and
* TASKANA only supports whole days. The format must be 'PnD'.
*/
public class MalformedServiceLevelException extends TaskanaException {
public static final String ERROR_KEY = "CLASSIFICATION_SERVICE_LEVEL_MALFORMED";
private final String serviceLevel;
private final String classificationKey;
private final String domain;
public MalformedServiceLevelException(
String serviceLevel, String classificationKey, String domain) {
super(
String.format(
"The provided service level '%s' of the "
+ "Classification with key '%s' and domain '%s' is invalid."
+ "The service level has to be a positive ISO-8601 duration format. "
+ "Furthermore, TASKANA only supports whole days; "
+ "the service level must be in the format 'PnD'",
serviceLevel, classificationKey, domain),
ErrorCode.of(
ERROR_KEY,
MapCreator.of(
"classificationKey",
classificationKey,
"domain",
domain,
"serviceLevel",
serviceLevel)));
this.serviceLevel = serviceLevel;
this.classificationKey = classificationKey;
this.domain = domain;
}
public String getServiceLevel() {
return serviceLevel;
}
public String getClassificationKey() {
return classificationKey;
}
public String getDomain() {
return domain;
}
}

View File

@ -338,7 +338,7 @@ public class ClassificationQueryImpl implements ClassificationQuery {
} catch (PersistenceException e) { } catch (PersistenceException e) {
if (e.getMessage().contains("ERRORCODE=-4470")) { if (e.getMessage().contains("ERRORCODE=-4470")) {
TaskanaRuntimeException ex = TaskanaRuntimeException ex =
new TaskanaRuntimeException( new SystemException(
"The offset beginning was set over the amount of result-rows.", e.getCause()); "The offset beginning was set over the amount of result-rows.", e.getCause());
ex.setStackTrace(e.getStackTrace()); ex.setStackTrace(e.getStackTrace());
throw ex; throw ex;

View File

@ -18,6 +18,7 @@ import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException; import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException;
import pro.taskana.classification.api.exceptions.ClassificationInUseException; import pro.taskana.classification.api.exceptions.ClassificationInUseException;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException; import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.classification.api.exceptions.MalformedServiceLevelException;
import pro.taskana.classification.api.models.Classification; import pro.taskana.classification.api.models.Classification;
import pro.taskana.classification.api.models.ClassificationSummary; import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.classification.internal.jobs.ClassificationChangedJob; import pro.taskana.classification.internal.jobs.ClassificationChangedJob;
@ -62,19 +63,17 @@ public class ClassificationServiceImpl implements ClassificationService {
public Classification getClassification(String key, String domain) public Classification getClassification(String key, String domain)
throws ClassificationNotFoundException { throws ClassificationNotFoundException {
if (key == null) { if (key == null) {
throw new ClassificationNotFoundException( throw new ClassificationNotFoundException(null, domain);
null, domain, "Classification for null key and domain " + domain + " was not found.");
} }
Classification result = null; Classification result;
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
result = classificationMapper.findByKeyAndDomain(key, domain); result = classificationMapper.findByKeyAndDomain(key, domain);
if (result == null) { if (result == null) {
result = classificationMapper.findByKeyAndDomain(key, ""); result = classificationMapper.findByKeyAndDomain(key, "");
if (result == null) { if (result == null) {
throw new ClassificationNotFoundException( throw new ClassificationNotFoundException(key, domain);
key, domain, "Classification for key = " + key + " and master domain was not found");
} }
} }
return result; return result;
@ -86,15 +85,14 @@ public class ClassificationServiceImpl implements ClassificationService {
@Override @Override
public Classification getClassification(String id) throws ClassificationNotFoundException { public Classification getClassification(String id) throws ClassificationNotFoundException {
if (id == null) { if (id == null) {
throw new ClassificationNotFoundException(null, "Classification for null id is invalid."); throw new ClassificationNotFoundException(null);
} }
Classification result = null; Classification result;
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
result = classificationMapper.findById(id); result = classificationMapper.findById(id);
if (result == null) { if (result == null) {
throw new ClassificationNotFoundException( throw new ClassificationNotFoundException(id);
id, "Classification for id " + id + " was not found");
} }
return result; return result;
} finally { } finally {
@ -110,8 +108,7 @@ public class ClassificationServiceImpl implements ClassificationService {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
Classification classification = this.classificationMapper.findById(classificationId); Classification classification = this.classificationMapper.findById(classificationId);
if (classification == null) { if (classification == null) {
throw new ClassificationNotFoundException( throw new ClassificationNotFoundException(classificationId);
classificationId, "The classification \"" + classificationId + "\" wasn't found");
} }
if (classification.getDomain().equals("")) { if (classification.getDomain().equals("")) {
@ -150,13 +147,7 @@ public class ClassificationServiceImpl implements ClassificationService {
} catch (PersistenceException e) { } catch (PersistenceException e) {
if (isReferentialIntegrityConstraintViolation(e)) { if (isReferentialIntegrityConstraintViolation(e)) {
throw new ClassificationInUseException( throw new ClassificationInUseException(classification, e);
String.format(
"The classification id = \"%s\" and key = \"%s\" in domain = \"%s\" "
+ "is in use and cannot be deleted. There are either tasks or "
+ "attachments associated with the classification.",
classificationId, classification.getKey(), classification.getDomain()),
e.getCause());
} }
} }
} finally { } finally {
@ -173,13 +164,7 @@ public class ClassificationServiceImpl implements ClassificationService {
Classification classification = Classification classification =
this.classificationMapper.findByKeyAndDomain(classificationKey, domain); this.classificationMapper.findByKeyAndDomain(classificationKey, domain);
if (classification == null) { if (classification == null) {
throw new ClassificationNotFoundException( throw new ClassificationNotFoundException(classificationKey, domain);
classificationKey,
domain,
"The classification \""
+ classificationKey
+ "\" wasn't found in the domain "
+ domain);
} }
deleteClassification(classification.getId()); deleteClassification(classification.getId());
} finally { } finally {
@ -190,13 +175,11 @@ public class ClassificationServiceImpl implements ClassificationService {
@Override @Override
public Classification createClassification(Classification classification) public Classification createClassification(Classification classification)
throws ClassificationAlreadyExistException, NotAuthorizedException, DomainNotFoundException, throws ClassificationAlreadyExistException, NotAuthorizedException, DomainNotFoundException,
InvalidArgumentException { InvalidArgumentException, MalformedServiceLevelException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
if (!taskanaEngine.domainExists(classification.getDomain()) if (!taskanaEngine.domainExists(classification.getDomain())
&& !"".equals(classification.getDomain())) { && !"".equals(classification.getDomain())) {
throw new DomainNotFoundException( throw new DomainNotFoundException(classification.getDomain());
classification.getDomain(),
"Domain " + classification.getDomain() + " does not exist in the configuration.");
} }
ClassificationImpl classificationImpl; ClassificationImpl classificationImpl;
final boolean isClassificationExisting; final boolean isClassificationExisting;
@ -246,14 +229,16 @@ public class ClassificationServiceImpl implements ClassificationService {
@Override @Override
public Classification updateClassification(Classification classification) public Classification updateClassification(Classification classification)
throws NotAuthorizedException, ConcurrencyException, ClassificationNotFoundException, throws NotAuthorizedException, ConcurrencyException, ClassificationNotFoundException,
InvalidArgumentException { InvalidArgumentException, MalformedServiceLevelException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
ClassificationImpl classificationImpl; ClassificationImpl classificationImpl;
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
if (classification.getKey().equals(classification.getParentKey())) { if (classification.getKey().equals(classification.getParentKey())) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
"The classification " + classification.getName() + " has the same key and parentKey"); String.format(
"The Classification '%s' has the same key and parent key",
classification.getName()));
} }
classificationImpl = (ClassificationImpl) classification; classificationImpl = (ClassificationImpl) classification;
@ -306,36 +291,26 @@ public class ClassificationServiceImpl implements ClassificationService {
return classification; return classification;
} }
private static void validateServiceLevel(String serviceLevel) throws InvalidArgumentException { private static void validateServiceLevel(Classification classification)
throws MalformedServiceLevelException {
String serviceLevel = classification.getServiceLevel();
Duration duration; Duration duration;
try { try {
duration = Duration.parse(serviceLevel); duration = Duration.parse(serviceLevel);
} catch (Exception e) { } catch (Exception e) {
throw new InvalidArgumentException( throw new MalformedServiceLevelException(
String.format( serviceLevel, classification.getKey(), classification.getDomain());
"Invalid service level %s. "
+ "The formats accepted are based on the ISO-8601 duration format "
+ "PnDTnHnMn.nS with days considered to be exactly 24 hours. "
+ "For example: \"P2D\" represents a period of \"two days.\" ",
serviceLevel),
e.getCause());
} }
if (duration.isNegative()) { if (duration.isNegative()) {
throw new InvalidArgumentException( throw new MalformedServiceLevelException(
String.format( serviceLevel, classification.getKey(), classification.getDomain());
"Invalid service level %s. Taskana does not support a negative service level.",
serviceLevel));
} }
// check that the duration is based on format PnD, i.e. it must start with a P, end with a D // check that the duration is based on format PnD, i.e. it must start with a P, end with a D
if (!serviceLevel.toLowerCase().startsWith("p") || !serviceLevel.toLowerCase().endsWith("d")) { if (!serviceLevel.toLowerCase().startsWith("p") || !serviceLevel.toLowerCase().endsWith("d")) {
throw new InvalidArgumentException( throw new MalformedServiceLevelException(
String.format( serviceLevel, classification.getKey(), classification.getDomain());
"Invalid service level %s. Taskana only supports service "
+ "levels that contain a number of whole days "
+ "specified according to the format ''PnD'' where n is the number of days",
serviceLevel));
} }
} }
@ -423,11 +398,13 @@ public class ClassificationServiceImpl implements ClassificationService {
* Fill missing values and validate classification before saving the classification. * Fill missing values and validate classification before saving the classification.
* *
* @param classification the classification which will be verified. * @param classification the classification which will be verified.
* @throws InvalidArgumentException if the given classification has no key, the service level is * @throws InvalidArgumentException if the given classification has no key, the type is not valid
* null, the type is not valid or the category for the provided type is invalid. * or the category for the provided type is invalid.
* @throws MalformedServiceLevelException if the given service level of the {@linkplain
* Classification} is invalid
*/ */
private void initDefaultClassificationValues(ClassificationImpl classification) private void initDefaultClassificationValues(ClassificationImpl classification)
throws InvalidArgumentException { throws InvalidArgumentException, MalformedServiceLevelException {
Instant now = Instant.now(); Instant now = Instant.now();
if (classification.getId() == null || "".equals(classification.getId())) { if (classification.getId() == null || "".equals(classification.getId())) {
classification.setId(IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_CLASSIFICATION)); classification.setId(IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_CLASSIFICATION));
@ -448,7 +425,7 @@ public class ClassificationServiceImpl implements ClassificationService {
if (classification.getServiceLevel() == null || classification.getServiceLevel().isEmpty()) { if (classification.getServiceLevel() == null || classification.getServiceLevel().isEmpty()) {
classification.setServiceLevel("P0D"); classification.setServiceLevel("P0D");
} else { } else {
validateServiceLevel(classification.getServiceLevel()); validateServiceLevel(classification);
} }
if (classification.getKey() == null) { if (classification.getKey() == null) {
@ -538,10 +515,7 @@ public class ClassificationServiceImpl implements ClassificationService {
Classification oldClassification = Classification oldClassification =
this.getClassification(classificationImpl.getKey(), classificationImpl.getDomain()); this.getClassification(classificationImpl.getKey(), classificationImpl.getDomain());
if (!oldClassification.getModified().equals(classificationImpl.getModified())) { if (!oldClassification.getModified().equals(classificationImpl.getModified())) {
throw new ConcurrencyException( throw new ConcurrencyException();
"The current Classification has been modified while editing. "
+ "The values can not be updated. Classification "
+ classificationImpl.toString());
} }
return oldClassification; return oldClassification;
} }

View File

@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import pro.taskana.common.api.ScheduledJob; import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.jobs.AbstractTaskanaJob; import pro.taskana.common.internal.jobs.AbstractTaskanaJob;
import pro.taskana.common.internal.transaction.TaskanaTransactionProvider; import pro.taskana.common.internal.transaction.TaskanaTransactionProvider;
@ -21,9 +22,9 @@ public class ClassificationChangedJob extends AbstractTaskanaJob {
public static final String PRIORITY_CHANGED = "priorityChanged"; public static final String PRIORITY_CHANGED = "priorityChanged";
public static final String SERVICE_LEVEL_CHANGED = "serviceLevelChanged"; public static final String SERVICE_LEVEL_CHANGED = "serviceLevelChanged";
private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationChangedJob.class); private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationChangedJob.class);
private String classificationId; private final String classificationId;
private boolean priorityChanged; private final boolean priorityChanged;
private boolean serviceLevelChanged; private final boolean serviceLevelChanged;
public ClassificationChangedJob( public ClassificationChangedJob(
TaskanaEngine engine, TaskanaTransactionProvider<Object> txProvider, ScheduledJob job) { TaskanaEngine engine, TaskanaTransactionProvider<Object> txProvider, ScheduledJob job) {
@ -46,7 +47,7 @@ public class ClassificationChangedJob extends AbstractTaskanaJob {
} }
LOGGER.info("ClassificationChangedJob ended successfully."); LOGGER.info("ClassificationChangedJob ended successfully.");
} catch (Exception e) { } catch (Exception e) {
throw new TaskanaException("Error while processing ClassificationChangedJob.", e); throw new SystemException("Error while processing ClassificationChangedJob.", e);
} }
} }

View File

@ -83,11 +83,11 @@ public interface TaskanaEngine {
void setConnectionManagementMode(ConnectionManagementMode mode); void setConnectionManagementMode(ConnectionManagementMode mode);
/** /**
* Set the connection to be used by taskana in mode CONNECTION_MANAGED_EXTERNALLY. If this Api is * Set the connection to be used by TASKANA in mode {@linkplain
* called, taskana uses the connection passed by the client for all subsequent API calls until the * ConnectionManagementMode#EXPLICIT}. If this Api is called, taskana uses the connection passed
* client resets this connection. Control over commit and rollback of the connection is the * by the client for all subsequent API calls until the client resets this connection. Control
* responsibility of the client. In order to close the connection, closeConnection() or * over commit and rollback of the connection is the responsibility of the client. In order to
* setConnection(null) has to be called. * close the connection, closeConnection() or setConnection(null) has to be called.
* *
* @param connection - The java.sql.Connection that is controlled by the client * @param connection - The java.sql.Connection that is controlled by the client
* @throws SQLException if a database access error occurs * @throws SQLException if a database access error occurs
@ -126,7 +126,6 @@ public interface TaskanaEngine {
*/ */
<T> T runAsAdmin(Supplier<T> supplier); <T> T runAsAdmin(Supplier<T> supplier);
/** /**
* Returns the CurrentUserContext class. * Returns the CurrentUserContext class.
* *

View File

@ -83,5 +83,4 @@ public interface InternalTaskanaEngine {
* @return the CreateTaskPreprocessorManager instance. * @return the CreateTaskPreprocessorManager instance.
*/ */
CreateTaskPreprocessorManager getCreateTaskPreprocessorManager(); CreateTaskPreprocessorManager getCreateTaskPreprocessorManager();
} }

View File

@ -36,9 +36,9 @@ import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.WorkingDaysToDaysConverter; import pro.taskana.common.api.WorkingDaysToDaysConverter;
import pro.taskana.common.api.exceptions.AutocommitFailedException; import pro.taskana.common.api.exceptions.AutocommitFailedException;
import pro.taskana.common.api.exceptions.ConnectionNotSetException; import pro.taskana.common.api.exceptions.ConnectionNotSetException;
import pro.taskana.common.api.exceptions.MismatchedRoleException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException; import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.common.api.security.CurrentUserContext; import pro.taskana.common.api.security.CurrentUserContext;
import pro.taskana.common.api.security.UserPrincipal; import pro.taskana.common.api.security.UserPrincipal;
import pro.taskana.common.internal.configuration.DB; import pro.taskana.common.internal.configuration.DB;
@ -239,23 +239,16 @@ public class TaskanaEngineImpl implements TaskanaEngine {
currentUserContext.getAccessIds(), currentUserContext.getAccessIds(),
rolesAsString); rolesAsString);
} }
throw new NotAuthorizedException( throw new MismatchedRoleException(currentUserContext.getUserid(), roles);
"current user is not member of role(s) " + Arrays.toString(roles),
currentUserContext.getUserid());
} }
} }
@Override
public CurrentUserContext getCurrentUserContext() {
return currentUserContext;
}
public <T> T runAsAdmin(Supplier<T> supplier) { public <T> T runAsAdmin(Supplier<T> supplier) {
String adminName = String adminName =
this.getConfiguration().getRoleMap().get(TaskanaRole.ADMIN).stream() this.getConfiguration().getRoleMap().get(TaskanaRole.ADMIN).stream()
.findFirst() .findFirst()
.orElseThrow(() -> new TaskanaRuntimeException("There is no admin configured")); .orElseThrow(() -> new SystemException("There is no admin configured"));
Subject subject = new Subject(); Subject subject = new Subject();
subject.getPrincipals().add(new UserPrincipal(adminName)); subject.getPrincipals().add(new UserPrincipal(adminName));
@ -263,6 +256,11 @@ public class TaskanaEngineImpl implements TaskanaEngine {
return Subject.doAs(subject, (PrivilegedAction<T>) supplier::get); return Subject.doAs(subject, (PrivilegedAction<T>) supplier::get);
} }
@Override
public CurrentUserContext getCurrentUserContext() {
return currentUserContext;
}
/** /**
* This method creates the sqlSessionManager of myBatis. It integrates all the SQL mappers and * This method creates the sqlSessionManager of myBatis. It integrates all the SQL mappers and
* sets the databaseId attribute. * sets the databaseId attribute.

View File

@ -1,10 +1,27 @@
package pro.taskana.spi.history.api.exceptions; package pro.taskana.spi.history.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotFoundException; import pro.taskana.common.api.exceptions.NotFoundException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
/**
* This exception is thrown when the {@linkplain TaskHistoryEvent} with the specified {@linkplain
* TaskHistoryEvent#getId() id} was not found.
*/
public class TaskanaHistoryEventNotFoundException extends NotFoundException { public class TaskanaHistoryEventNotFoundException extends NotFoundException {
public TaskanaHistoryEventNotFoundException(String id, String msg) { public static final String ERROR_KEY = "HISTORY_EVENT_NOT_FOUND";
super(id, msg); private final String historyEventId;
public TaskanaHistoryEventNotFoundException(String historyEventId) {
super(
String.format("TaskHistoryEvent with id '%s' was not found", historyEventId),
ErrorCode.of(ERROR_KEY, MapCreator.of("historyEventId", historyEventId)));
this.historyEventId = historyEventId;
}
public String getHistoryEventId() {
return historyEventId;
} }
} }

View File

@ -60,7 +60,7 @@ public interface TaskService {
* *
* @param taskId id of the task which should be unclaimed. * @param taskId id of the task which should be unclaimed.
* @return updated unclaimed task * @return updated unclaimed task
* @throws TaskNotFoundException if the task can´t be found or does not exist * @throws TaskNotFoundException if the task can't be found or does not exist
* @throws InvalidStateException if the task is already in an end state. * @throws InvalidStateException if the task is already in an end state.
* @throws InvalidOwnerException if the task is claimed by another user. * @throws InvalidOwnerException if the task is claimed by another user.
* @throws NotAuthorizedException if the current user has no read permission for the workbasket * @throws NotAuthorizedException if the current user has no read permission for the workbasket
@ -75,7 +75,7 @@ public interface TaskService {
* *
* @param taskId id of the task which should be unclaimed. * @param taskId id of the task which should be unclaimed.
* @return updated unclaimed task * @return updated unclaimed task
* @throws TaskNotFoundException if the task can´t be found or does not exist * @throws TaskNotFoundException if the task can't be found or does not exist
* @throws InvalidStateException if the task is already in an end state. * @throws InvalidStateException if the task is already in an end state.
* @throws InvalidOwnerException if forceCancel is false and the task is claimed by another user. * @throws InvalidOwnerException if forceCancel is false and the task is claimed by another user.
* @throws NotAuthorizedException if the current user has no read permission for the workbasket * @throws NotAuthorizedException if the current user has no read permission for the workbasket
@ -91,8 +91,8 @@ public interface TaskService {
* *
* @param taskId - Id of the Task which should be completed. * @param taskId - Id of the Task which should be completed.
* @return Task - updated task after completion. * @return Task - updated task after completion.
* @throws InvalidStateException if Task wasn´t claimed before. * @throws InvalidStateException if Task wasn't claimed before.
* @throws TaskNotFoundException if the given Task can´t be found in DB. * @throws TaskNotFoundException if the given Task can't be found in DB.
* @throws InvalidOwnerException if current user is not the task-owner or administrator. * @throws InvalidOwnerException if current user is not the task-owner or administrator.
* @throws NotAuthorizedException if the current user has no read permission for the workbasket * @throws NotAuthorizedException if the current user has no read permission for the workbasket
* the task is in * the task is in
@ -107,8 +107,8 @@ public interface TaskService {
* *
* @param taskId - Id of the Task which should be completed. * @param taskId - Id of the Task which should be completed.
* @return Task - updated task after completion. * @return Task - updated task after completion.
* @throws InvalidStateException if Task wasn´t claimed before. * @throws InvalidStateException if Task wasn't claimed before.
* @throws TaskNotFoundException if the given Task can´t be found in DB. * @throws TaskNotFoundException if the given Task can't be found in DB.
* @throws InvalidOwnerException if current user is not the task-owner or administrator. * @throws InvalidOwnerException if current user is not the task-owner or administrator.
* @throws NotAuthorizedException if the current user has no read permission for the workbasket * @throws NotAuthorizedException if the current user has no read permission for the workbasket
* the task is in * the task is in

View File

@ -13,7 +13,7 @@ public enum TaskState {
public static final TaskState[] END_STATES = {COMPLETED, CANCELLED, TERMINATED}; public static final TaskState[] END_STATES = {COMPLETED, CANCELLED, TERMINATED};
public boolean in(TaskState... states) { public boolean in(TaskState... states) {
return Arrays.stream(states).anyMatch(state -> state == this); return Arrays.asList(states).contains(this);
} }
public boolean isEndState() { public boolean isEndState() {

View File

@ -1,15 +1,40 @@
package pro.taskana.task.api.exceptions; package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.Attachment;
import pro.taskana.task.api.models.Task;
/** /**
* Thrown, when an attachment should be inserted to the DB, but it does already exist.<br> * This exception is thrown when an {@linkplain Attachment} should be inserted to the DB, but it
* This may happen when a not inserted attachment with ID will be added twice on a task. This can´t * does already exist. <br>
* be happen it the correct Task-Methods will be used instead the List ones. * This may happen when a not inserted {@linkplain Attachment} with the same {@linkplain
* Attachment#getId() id} will be added twice on a {@linkplain Task}. This can't happen if the
* correct {@linkplain Task}-Methods will be used instead of the List ones.
*/ */
public class AttachmentPersistenceException extends TaskanaException { public class AttachmentPersistenceException extends TaskanaException {
public static final String ERROR_KEY = "ATTACHMENT_ALREADY_EXISTS";
private final String attachmentId;
private final String taskId;
public AttachmentPersistenceException(String msg, Throwable cause) { public AttachmentPersistenceException(String attachmentId, String taskId, Throwable cause) {
super(msg, cause); super(
String.format(
"Cannot insert Attachment with id '%s' for Task with id '%s' "
+ "because it already exists.",
attachmentId, taskId),
ErrorCode.of(ERROR_KEY, MapCreator.of("attachmentId", attachmentId, "taskId", taskId)),
cause);
this.attachmentId = attachmentId;
this.taskId = taskId;
}
public String getAttachmentId() {
return attachmentId;
}
public String getTaskId() {
return taskId;
} }
} }

View File

@ -0,0 +1,53 @@
package pro.taskana.task.api.exceptions;
import java.util.Arrays;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.CallbackState;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.internal.models.MinimalTaskSummary;
/**
* This exception is thrown when the {@linkplain MinimalTaskSummary#getCallbackState() callback
* state} of the {@linkplain Task} doesn't allow the requested operation.
*/
public class InvalidCallbackStateException extends InvalidStateException {
public static final String ERROR_KEY = "TASK_INVALID_CALLBACK_STATE";
private final String taskId;
private final CallbackState taskCallbackState;
private final CallbackState[] requiredCallbackStates;
public InvalidCallbackStateException(
String taskId, CallbackState taskCallbackState, CallbackState... requiredCallbackStates) {
super(
String.format(
"Expected callback state of Task with id '%s' to be: '%s', but found '%s'",
taskId, Arrays.toString(requiredCallbackStates), taskCallbackState),
ErrorCode.of(
ERROR_KEY,
MapCreator.of(
"taskId",
taskId,
"taskCallbackState",
taskCallbackState,
"requiredCallbackStates",
requiredCallbackStates)));
this.taskId = taskId;
this.taskCallbackState = taskCallbackState;
this.requiredCallbackStates = requiredCallbackStates;
}
public CallbackState getTaskCallbackState() {
return taskCallbackState;
}
public CallbackState[] getRequiredCallbackStates() {
return requiredCallbackStates;
}
public String getTaskId() {
return taskId;
}
}

View File

@ -1,11 +1,29 @@
package pro.taskana.task.api.exceptions; package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.Task;
/** This exception is thrown when the task state doesn't allow the requested operation. */ /** This exception is thrown when the current user is not the owner of the {@linkplain Task}. */
public class InvalidOwnerException extends TaskanaException { public class InvalidOwnerException extends TaskanaException {
public static final String ERROR_KEY = "TASK_INVALID_OWNER";
private final String taskId;
private final String currentUserId;
public InvalidOwnerException(String msg) { public InvalidOwnerException(String currentUserId, String taskId) {
super(msg); super(
String.format("User '%s' is not owner of Task '%s'.", currentUserId, taskId),
ErrorCode.of(ERROR_KEY, MapCreator.of("taskId", taskId, "currentUserId", currentUserId)));
this.taskId = taskId;
this.currentUserId = currentUserId;
}
public String getTaskId() {
return taskId;
}
public String getCurrentUserId() {
return currentUserId;
} }
} }

View File

@ -1,11 +1,12 @@
package pro.taskana.task.api.exceptions; package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
/** This exception is thrown when the task state doesn't allow the requested operation. */ /** This exception is thrown when the current state doesn't allow the requested operation. */
public class InvalidStateException extends TaskanaException { public class InvalidStateException extends TaskanaException {
public InvalidStateException(String msg) { protected InvalidStateException(String msg, ErrorCode errorCode) {
super(msg); super(msg, errorCode);
} }
} }

View File

@ -0,0 +1,53 @@
package pro.taskana.task.api.exceptions;
import java.util.Arrays;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.models.Task;
/**
* This exception is thrown when the {@linkplain Task#getState() state} of the {@linkplain Task}
* doesn't allow the requested operation.
*/
public class InvalidTaskStateException extends InvalidStateException {
public static final String ERROR_KEY = "TASK_INVALID_STATE";
private final String taskId;
private final TaskState taskState;
private final TaskState[] requiredTaskStates;
public InvalidTaskStateException(
String taskId, TaskState taskState, TaskState... requiredTaskStates) {
super(
String.format(
"Task with id '%s' is in state: '%s', but must be in one of these states: '%s'",
taskId, taskState, Arrays.toString(requiredTaskStates)),
ErrorCode.of(
ERROR_KEY,
MapCreator.of(
"taskId",
taskId,
"taskState",
taskState,
"requiredTaskStates",
requiredTaskStates)));
this.taskId = taskId;
this.taskState = taskState;
this.requiredTaskStates = requiredTaskStates;
}
public String getTaskId() {
return taskId;
}
public TaskState getTaskState() {
return taskState;
}
public TaskState[] getRequiredTaskStates() {
return requiredTaskStates;
}
}

View File

@ -0,0 +1,38 @@
package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.TaskComment;
/**
* This exception is thrown when the current user is not the creator of the {@linkplain TaskComment}
* it tries to modify.
*/
public class MismatchedTaskCommentCreatorException extends NotAuthorizedException {
public static final String ERROR_KEY = "TASK_COMMENT_CREATOR_MISMATCHED";
private final String currentUserId;
private final String taskCommentId;
public MismatchedTaskCommentCreatorException(String currentUserId, String taskCommentId) {
super(
String.format(
"Not authorized. Current user '%s' is not the creator of TaskComment with id '%s'.",
currentUserId, taskCommentId),
ErrorCode.of(
ERROR_KEY,
MapCreator.of("currentUserId", currentUserId, "taskCommentId", taskCommentId)));
this.currentUserId = currentUserId;
this.taskCommentId = taskCommentId;
}
public String getCurrentUserId() {
return currentUserId;
}
public String getTaskCommentId() {
return taskCommentId;
}
}

View File

@ -1,14 +1,27 @@
package pro.taskana.task.api.exceptions; package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.Task;
/** /**
* Thrown when a Task is going to be created, but a Task with the same ID does already exist. The * This exception is thrown when a {@linkplain Task} is going to be created, but a {@linkplain Task}
* Task ID should be unique. * with the same {@linkplain Task#getExternalId() external id} does already exist.
*/ */
public class TaskAlreadyExistException extends TaskanaException { public class TaskAlreadyExistException extends TaskanaException {
public TaskAlreadyExistException(String id) { public static final String ERROR_KEY = "TASK_ALREADY_EXISTS";
super(id); private final String externalId;
public TaskAlreadyExistException(String externalId) {
super(
String.format("Task with external id '%s' already exists", externalId),
ErrorCode.of(ERROR_KEY, MapCreator.of("externalTaskId", externalId)));
this.externalId = externalId;
}
public String getExternalId() {
return externalId;
} }
} }

View File

@ -1,11 +1,24 @@
package pro.taskana.task.api.exceptions; package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotFoundException; import pro.taskana.common.api.exceptions.NotFoundException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.TaskComment;
/** This exception will be thrown if a specific task comment is not in the database. */ /** This exception is thrown when a specific {@linkplain TaskComment} is not in the database. */
public class TaskCommentNotFoundException extends NotFoundException { public class TaskCommentNotFoundException extends NotFoundException {
public TaskCommentNotFoundException(String id, String msg) { public static final String ERROR_KEY = "TASK_COMMENT_NOT_FOUND";
super(id, msg); private final String taskCommentId;
public TaskCommentNotFoundException(String taskCommentId) {
super(
String.format("TaskComment with id '%s' was not found", taskCommentId),
ErrorCode.of(ERROR_KEY, MapCreator.of("taskCommentId", taskCommentId)));
this.taskCommentId = taskCommentId;
}
public String getTaskCommentId() {
return taskCommentId;
} }
} }

View File

@ -1,11 +1,24 @@
package pro.taskana.task.api.exceptions; package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotFoundException; import pro.taskana.common.api.exceptions.NotFoundException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.task.api.models.Task;
/** This exception will be thrown if a specific task is not in the database. */ /** This exception is thrown when a specific {@linkplain Task} is not in the database. */
public class TaskNotFoundException extends NotFoundException { public class TaskNotFoundException extends NotFoundException {
public TaskNotFoundException(String id, String msg) { public static final String ERROR_KEY = "TASK_NOT_FOUND";
super(id, msg); private final String taskId;
public TaskNotFoundException(String taskId) {
super(
String.format("Task with id '%s' was not found.", taskId),
ErrorCode.of(ERROR_KEY, MapCreator.of("taskId", taskId)));
this.taskId = taskId;
}
public String getTaskId() {
return taskId;
} }
} }

View File

@ -1,11 +0,0 @@
package pro.taskana.task.api.exceptions;
import pro.taskana.common.api.exceptions.TaskanaException;
/** This exception will be thrown if a specific task is not in the database. */
public class UpdateFailedException extends TaskanaException {
public UpdateFailedException(String msg) {
super(msg);
}
}

View File

@ -119,7 +119,7 @@ public interface Task extends TaskSummary {
/** /**
* Return the attachments for this task. <br> * Return the attachments for this task. <br>
* Do not use List.add()/addAll() for adding Elements, because it can cause redundant data. Use * Do not use List.add()/addAll() for adding Elements, because it can cause redundant data. Use
* addAttachment(). Clear() and remove() can be used, because it´s a controllable change. * addAttachment(). Clear() and remove() can be used, because it's a controllable change.
* *
* @return the {@link List list} of {@link Attachment attachments} for this task * @return the {@link List list} of {@link Attachment attachments} for this task
*/ */

View File

@ -14,7 +14,6 @@ import org.slf4j.LoggerFactory;
import pro.taskana.classification.api.ClassificationService; import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException; import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.classification.api.models.ClassificationSummary; import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.internal.util.IdGenerator; import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.task.api.exceptions.AttachmentPersistenceException; import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
@ -36,46 +35,6 @@ public class AttachmentHandler {
this.classificationService = classificationService; this.classificationService = classificationService;
} }
List<Attachment> augmentAttachmentsByClassification(
List<AttachmentImpl> attachmentImpls, BulkOperationResults<String, Exception> bulkLog) {
List<Attachment> result = new ArrayList<>();
if (attachmentImpls == null || attachmentImpls.isEmpty()) {
return result;
}
List<ClassificationSummary> classifications =
classificationService
.createClassificationQuery()
.idIn(
attachmentImpls.stream()
.map(t -> t.getClassificationSummary().getId())
.distinct()
.toArray(String[]::new))
.list();
for (AttachmentImpl att : attachmentImpls) {
ClassificationSummary classificationSummary =
classifications.stream()
.filter(cl -> cl.getId().equals(att.getClassificationSummary().getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
String id = att.getClassificationSummary().getId();
bulkLog.addError(
att.getClassificationSummary().getId(),
new ClassificationNotFoundException(
id,
String.format(
"When processing task updates due to change "
+ "of classification, the classification with id %s was not found",
id)));
} else {
att.setClassificationSummary(classificationSummary);
result.add(att);
}
}
return result;
}
void insertAndDeleteAttachmentsOnTaskUpdate(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) void insertAndDeleteAttachmentsOnTaskUpdate(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
throws AttachmentPersistenceException, InvalidArgumentException, throws AttachmentPersistenceException, InvalidArgumentException,
ClassificationNotFoundException { ClassificationNotFoundException {
@ -113,11 +72,7 @@ public class AttachmentHandler {
attachmentImpl); attachmentImpl);
} }
} catch (PersistenceException e) { } catch (PersistenceException e) {
throw new AttachmentPersistenceException( throw new AttachmentPersistenceException(attachmentImpl.getId(), task.getId(), e);
String.format(
"Cannot insert the Attachement %s for Task %s because it already exists.",
attachmentImpl.getId(), task.getId()),
e.getCause());
} }
} }
} }
@ -199,11 +154,7 @@ public class AttachmentHandler {
attachmentImpl); attachmentImpl);
} }
} catch (PersistenceException e) { } catch (PersistenceException e) {
throw new AttachmentPersistenceException( throw new AttachmentPersistenceException(attachmentImpl.getId(), newTaskImpl.getId(), e);
String.format(
"Cannot insert the Attachement %s for Task %s because it already exists.",
attachmentImpl.getId(), newTaskImpl.getId()),
e.getCause());
} }
} }

View File

@ -21,7 +21,7 @@ import pro.taskana.common.api.WorkingDaysToDaysConverter;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.task.api.exceptions.UpdateFailedException; import pro.taskana.common.internal.util.Pair;
import pro.taskana.task.api.models.Attachment; import pro.taskana.task.api.models.Attachment;
import pro.taskana.task.api.models.AttachmentSummary; import pro.taskana.task.api.models.AttachmentSummary;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
@ -38,6 +38,7 @@ class ServiceLevelHandler {
private final TaskMapper taskMapper; private final TaskMapper taskMapper;
private final AttachmentMapper attachmentMapper; private final AttachmentMapper attachmentMapper;
private final WorkingDaysToDaysConverter converter; private final WorkingDaysToDaysConverter converter;
private final TaskServiceImpl taskServiceImpl;
ServiceLevelHandler( ServiceLevelHandler(
InternalTaskanaEngine taskanaEngine, InternalTaskanaEngine taskanaEngine,
@ -47,6 +48,7 @@ class ServiceLevelHandler {
this.taskMapper = taskMapper; this.taskMapper = taskMapper;
this.attachmentMapper = attachmentMapper; this.attachmentMapper = attachmentMapper;
converter = taskanaEngine.getEngine().getWorkingDaysToDaysConverter(); converter = taskanaEngine.getEngine().getWorkingDaysToDaysConverter();
taskServiceImpl = (TaskServiceImpl) taskanaEngine.getEngine().getTaskService();
} }
// use the same algorithm as setPlannedPropertyOfTasksImpl to refresh // use the same algorithm as setPlannedPropertyOfTasksImpl to refresh
@ -351,7 +353,7 @@ class ServiceLevelHandler {
private BulkOperationResults<String, TaskanaException> private BulkOperationResults<String, TaskanaException>
updateDuePropertyOfTasksWithIdenticalDueDate( updateDuePropertyOfTasksWithIdenticalDueDate(
InstantDurationHolder durationHolder, List<TaskDuration> taskDurationList) { InstantDurationHolder durationHolder, List<TaskDuration> taskDurationList) {
BulkLog bulkLog = new BulkLog(); final BulkLog bulkLog = new BulkLog();
TaskImpl referenceTask = new TaskImpl(); TaskImpl referenceTask = new TaskImpl();
referenceTask.setPlanned(durationHolder.getPlanned()); referenceTask.setPlanned(durationHolder.getPlanned());
referenceTask.setModified(Instant.now()); referenceTask.setModified(Instant.now());
@ -359,61 +361,30 @@ class ServiceLevelHandler {
getFollowingWorkingDays(referenceTask.getPlanned(), durationHolder.getDuration())); getFollowingWorkingDays(referenceTask.getPlanned(), durationHolder.getDuration()));
List<String> taskIdsToUpdate = List<String> taskIdsToUpdate =
taskDurationList.stream().map(TaskDuration::getTaskId).collect(Collectors.toList()); taskDurationList.stream().map(TaskDuration::getTaskId).collect(Collectors.toList());
long numTasksUpdated = taskMapper.updateTaskDueDates(taskIdsToUpdate, referenceTask); Pair<List<MinimalTaskSummary>, BulkLog> existingAndAuthorizedTasks =
if (numTasksUpdated != taskIdsToUpdate.size()) { taskServiceImpl.getMinimalTaskSummaries(taskIdsToUpdate);
BulkLog checkResult = bulkLog.addAllErrors(existingAndAuthorizedTasks.getRight());
checkResultsOfTasksUpdateAndAddErrorsToBulkLog(
taskIdsToUpdate, referenceTask, numTasksUpdated); taskMapper.updateTaskDueDates(existingAndAuthorizedTasks.getLeft(), referenceTask);
bulkLog.addAllErrors(checkResult);
}
return bulkLog; return bulkLog;
} }
private BulkLog updatePlannedPropertyOfAffectedTasks( private BulkLog updatePlannedPropertyOfAffectedTasks(
Instant planned, Map<Duration, List<String>> durationToTaskIdsMap) { Instant planned, Map<Duration, List<String>> taskIdsByDueDuration) {
BulkLog bulkLog = new BulkLog(); final BulkLog bulkLog = new BulkLog();
TaskImpl referenceTask = new TaskImpl(); TaskImpl referenceTask = new TaskImpl();
referenceTask.setPlanned(planned); referenceTask.setPlanned(planned);
referenceTask.setModified(Instant.now()); referenceTask.setModified(Instant.now());
for (Map.Entry<Duration, List<String>> entry : durationToTaskIdsMap.entrySet()) {
List<String> taskIdsToUpdate = entry.getValue();
referenceTask.setDue(getFollowingWorkingDays(referenceTask.getPlanned(), entry.getKey()));
long numTasksUpdated = taskMapper.updateTaskDueDates(taskIdsToUpdate, referenceTask);
if (numTasksUpdated != taskIdsToUpdate.size()) {
BulkLog checkResult =
checkResultsOfTasksUpdateAndAddErrorsToBulkLog(
taskIdsToUpdate, referenceTask, numTasksUpdated);
bulkLog.addAllErrors(checkResult);
}
}
return bulkLog;
}
private BulkLog checkResultsOfTasksUpdateAndAddErrorsToBulkLog( taskIdsByDueDuration.forEach(
List<String> taskIdsToUpdate, TaskImpl referenceTask, long numTasksUpdated) { (duration, taskIds) -> {
BulkLog bulkLog = new BulkLog(); referenceTask.setDue(getFollowingWorkingDays(planned, duration));
long numErrors = taskIdsToUpdate.size() - numTasksUpdated; Pair<List<MinimalTaskSummary>, BulkLog> existingAndAuthorizedTasks =
long numErrorsLogged = 0; taskServiceImpl.getMinimalTaskSummaries(taskIds);
if (numErrors > 0) { bulkLog.addAllErrors(existingAndAuthorizedTasks.getRight());
List<MinimalTaskSummary> taskSummaries = taskMapper.findExistingTasks(taskIdsToUpdate, null); taskMapper.updateTaskDueDates(existingAndAuthorizedTasks.getLeft(), referenceTask);
for (MinimalTaskSummary task : taskSummaries) { });
if (referenceTask.getDue() != task.getDue()) {
bulkLog.addError(
task.getTaskId(),
new UpdateFailedException(
String.format("Could not set Due Date of Task with Id %s. ", task.getTaskId())));
numErrorsLogged++;
}
}
long numErrorsNotLogged = numErrors - numErrorsLogged;
if (numErrorsNotLogged != 0) {
for (int i = 1; i <= numErrorsNotLogged; i++) {
bulkLog.addError(
String.format("UnknownTaskId: %d", i),
new UpdateFailedException("Update of unknown task failed"));
}
}
}
return bulkLog; return bulkLog;
} }
@ -641,17 +612,12 @@ class ServiceLevelHandler {
&& newClassificationIds.containsAll(oldClassificationIds); && newClassificationIds.containsAll(oldClassificationIds);
} }
static class BulkLog extends BulkOperationResults<String, TaskanaException> { static class BulkLog extends BulkOperationResults<String, TaskanaException> {}
BulkLog() {
super();
}
}
static final class DurationPrioHolder { static final class DurationPrioHolder {
private Duration duration; private final Duration duration;
private int priority; private final int priority;
DurationPrioHolder(Duration duration, int priority) { DurationPrioHolder(Duration duration, int priority) {
this.duration = duration; this.duration = duration;

View File

@ -13,6 +13,7 @@ import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException; import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.util.IdGenerator; import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.task.api.exceptions.MismatchedTaskCommentCreatorException;
import pro.taskana.task.api.exceptions.TaskCommentNotFoundException; import pro.taskana.task.api.exceptions.TaskCommentNotFoundException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.TaskComment; import pro.taskana.task.api.models.TaskComment;
@ -75,12 +76,7 @@ class TaskCommentServiceImpl {
} }
} else { } else {
throw new NotAuthorizedException( throw new MismatchedTaskCommentCreatorException(userId, taskCommentImplToUpdate.getId());
String.format(
"Not authorized, TaskComment creator and current user must match. "
+ "TaskComment creator is %s but current user is %s",
taskCommentImplToUpdate.getCreator(), userId),
userId);
} }
} finally { } finally {
taskanaEngine.returnConnection(); taskanaEngine.returnConnection();
@ -136,12 +132,7 @@ class TaskCommentServiceImpl {
} }
} else { } else {
throw new NotAuthorizedException( throw new MismatchedTaskCommentCreatorException(userId, taskCommentToDelete.getId());
String.format(
"Not authorized, TaskComment creator and current user must match. "
+ "TaskComment creator is %s but current user is %s",
taskCommentToDelete.getCreator(), userId),
userId);
} }
} finally { } finally {
@ -186,9 +177,7 @@ class TaskCommentServiceImpl {
result = taskCommentMapper.findById(taskCommentId); result = taskCommentMapper.findById(taskCommentId);
if (result == null) { if (result == null) {
throw new TaskCommentNotFoundException( throw new TaskCommentNotFoundException(taskCommentId);
taskCommentId,
String.format("TaskComment for taskCommentId '%s' was not found", taskCommentId));
} }
taskService.getTask(result.getTaskId()); taskService.getTask(result.getTaskId());
@ -204,11 +193,7 @@ class TaskCommentServiceImpl {
TaskComment oldTaskComment, TaskComment taskCommentImplToUpdate) throws ConcurrencyException { TaskComment oldTaskComment, TaskComment taskCommentImplToUpdate) throws ConcurrencyException {
if (!oldTaskComment.getModified().equals(taskCommentImplToUpdate.getModified())) { if (!oldTaskComment.getModified().equals(taskCommentImplToUpdate.getModified())) {
throw new ConcurrencyException();
throw new ConcurrencyException(
"The current TaskComment has been modified while editing. "
+ "The values can not be updated. TaskComment "
+ taskCommentImplToUpdate.toString());
} }
} }

View File

@ -1,6 +1,7 @@
package pro.taskana.task.internal; package pro.taskana.task.internal;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -131,10 +132,9 @@ public interface TaskMapper {
@Update( @Update(
"<script>UPDATE TASK SET OWNER = #{owner}, MODIFIED = #{modified} " "<script>UPDATE TASK SET OWNER = #{owner}, MODIFIED = #{modified} "
+ "WHERE STATE = 'READY' " + "WHERE ID IN <foreach item='taskId' index='index' separator=',' open='(' close=')' collection='taskIds'>#{taskId}</foreach> "
+ "AND ID IN <foreach item='taskId' index='index' separator=',' open='(' close=')' collection='taskIds'>#{taskId}</foreach> "
+ "</script>") + "</script>")
int setOwnerOfTasks( void setOwnerOfTasks(
@Param("owner") String owner, @Param("owner") String owner,
@Param("taskIds") List<String> taskIds, @Param("taskIds") List<String> taskIds,
@Param("modified") Instant modified); @Param("modified") Instant modified);
@ -184,7 +184,7 @@ public interface TaskMapper {
@Result(property = "planned", column = "PLANNED") @Result(property = "planned", column = "PLANNED")
@Result(property = "callbackState", column = "CALLBACK_STATE") @Result(property = "callbackState", column = "CALLBACK_STATE")
List<MinimalTaskSummary> findExistingTasks( List<MinimalTaskSummary> findExistingTasks(
@Param("taskIds") List<String> taskIds, @Param("externalIds") List<String> externalIds); @Param("taskIds") Collection<String> taskIds, @Param("externalIds") List<String> externalIds);
@Update( @Update(
"<script>" "<script>"
@ -222,14 +222,15 @@ public interface TaskMapper {
@Update( @Update(
"<script>" "<script>"
+ "<if test='taskIds != null'> " + "<if test='taskSummaries != null'> "
+ "UPDATE TASK SET MODIFIED = #{referenceTask.modified}, " + "UPDATE TASK SET MODIFIED = #{referenceTask.modified}, "
+ "PLANNED = #{referenceTask.planned}, DUE = #{referenceTask.due} " + "PLANNED = #{referenceTask.planned}, DUE = #{referenceTask.due} "
+ "WHERE ID IN(<foreach item='item' collection='taskIds' separator=',' >#{item}</foreach>) " + "WHERE ID IN(<foreach item='taskSummary' collection='taskSummaries' separator=',' >#{taskSummary.taskId}</foreach>) "
+ "</if> " + "</if> "
+ "</script>") + "</script>")
long updateTaskDueDates( void updateTaskDueDates(
@Param("taskIds") List<String> taskIds, @Param("referenceTask") TaskImpl referenceTask); @Param("taskSummaries") List<MinimalTaskSummary> taskSummaries,
@Param("referenceTask") TaskImpl referenceTask);
@Update( @Update(
"<script>" "<script>"
@ -239,7 +240,7 @@ public interface TaskMapper {
+ "WHERE ID IN(<foreach item='item' collection='taskIds' separator=',' >#{item}</foreach>) " + "WHERE ID IN(<foreach item='item' collection='taskIds' separator=',' >#{item}</foreach>) "
+ "</if> " + "</if> "
+ "</script>") + "</script>")
long updatePriorityOfTasks( void updatePriorityOfTasks(
@Param("taskIds") List<String> taskIds, @Param("referenceTask") TaskImpl referenceTask); @Param("taskIds") List<String> taskIds, @Param("referenceTask") TaskImpl referenceTask);
@Select( @Select(
@ -261,10 +262,10 @@ public interface TaskMapper {
"<script> " "<script> "
+ "<choose>" + "<choose>"
+ "<when test='accessIds == null'>" + "<when test='accessIds == null'>"
+ "SELECT t.ID FROM TASK t WHERE 1 = 2 " + "SELECT NULL LIMIT 0 "
+ "</when>" + "</when>"
+ "<otherwise>" + "<otherwise>"
+ "SELECT t.ID FROM TASK t WHERE t.ID IN(<foreach item='item' collection='taskIds' separator=',' >#{item}</foreach>)" + "SELECT t.ID, t.WORKBASKET_ID FROM TASK t WHERE t.ID IN(<foreach item='taskSummary' collection='taskSummaries' separator=',' >#{taskSummary.taskId}</foreach>)"
+ "AND NOT (t.WORKBASKET_ID IN ( " + "AND NOT (t.WORKBASKET_ID IN ( "
+ "<choose>" + "<choose>"
+ "<when test=\"_databaseId == 'db2'\">" + "<when test=\"_databaseId == 'db2'\">"
@ -279,7 +280,9 @@ public interface TaskMapper {
+ "</otherwise>" + "</otherwise>"
+ "</choose>" + "</choose>"
+ "</script>") + "</script>")
@Result(property = "id", column = "ID") @Result(property = "left", column = "ID")
List<String> filterTaskIdsNotAuthorizedFor( @Result(property = "right", column = "WORKBASKET_ID")
@Param("taskIds") List<String> taskIds, @Param("accessIds") List<String> accessIds); List<Pair<String, String>> getTaskAndWorkbasketIdsNotAuthorizedFor(
@Param("taskSummaries") List<MinimalTaskSummary> taskSummaries,
@Param("accessIds") List<String> accessIds);
} }

View File

@ -2,7 +2,6 @@ package pro.taskana.task.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
@ -18,6 +17,7 @@ import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException; import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.configuration.DB; import pro.taskana.common.internal.configuration.DB;
import pro.taskana.common.internal.util.EnumUtil;
import pro.taskana.task.api.CallbackState; import pro.taskana.task.api.CallbackState;
import pro.taskana.task.api.ObjectReferenceQuery; import pro.taskana.task.api.ObjectReferenceQuery;
import pro.taskana.task.api.TaskCustomField; import pro.taskana.task.api.TaskCustomField;
@ -231,8 +231,7 @@ public class TaskQueryImpl implements TaskQuery {
@Override @Override
public TaskQuery stateNotIn(TaskState... states) { public TaskQuery stateNotIn(TaskState... states) {
this.stateIn = this.stateIn = EnumUtil.allValuesExceptFor(states);
EnumSet.complementOf(EnumSet.copyOf(Arrays.asList(states))).toArray(new TaskState[0]);
return this; return this;
} }
@ -947,7 +946,7 @@ public class TaskQueryImpl implements TaskQuery {
} catch (PersistenceException e) { } catch (PersistenceException e) {
if (e.getMessage().contains("ERRORCODE=-4470")) { if (e.getMessage().contains("ERRORCODE=-4470")) {
TaskanaRuntimeException ex = TaskanaRuntimeException ex =
new TaskanaRuntimeException( new SystemException(
"The offset beginning was set over the amount of result-rows.", e.getCause()); "The offset beginning was set over the amount of result-rows.", e.getCause());
ex.setStackTrace(e.getStackTrace()); ex.setStackTrace(e.getStackTrace());
throw ex; throw ex;
@ -1619,7 +1618,7 @@ public class TaskQueryImpl implements TaskQuery {
} }
} }
} catch (NotAuthorizedException e) { } catch (NotAuthorizedException e) {
throw new NotAuthorizedToQueryWorkbasketException(e.getMessage(), e.getCause()); throw new NotAuthorizedToQueryWorkbasketException(e.getMessage(), e.getErrorCode(), e);
} }
} }

View File

@ -3,6 +3,7 @@ package pro.taskana.task.internal;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -34,6 +35,7 @@ import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.util.CheckedConsumer; import pro.taskana.common.internal.util.CheckedConsumer;
import pro.taskana.common.internal.util.CollectionUtil; import pro.taskana.common.internal.util.CollectionUtil;
import pro.taskana.common.internal.util.EnumUtil;
import pro.taskana.common.internal.util.IdGenerator; import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.common.internal.util.ObjectAttributeChangeDetector; import pro.taskana.common.internal.util.ObjectAttributeChangeDetector;
import pro.taskana.common.internal.util.Pair; import pro.taskana.common.internal.util.Pair;
@ -52,12 +54,13 @@ import pro.taskana.task.api.TaskQuery;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.AttachmentPersistenceException; import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
import pro.taskana.task.api.exceptions.InvalidCallbackStateException;
import pro.taskana.task.api.exceptions.InvalidOwnerException; import pro.taskana.task.api.exceptions.InvalidOwnerException;
import pro.taskana.task.api.exceptions.InvalidStateException; import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.TaskAlreadyExistException; import pro.taskana.task.api.exceptions.TaskAlreadyExistException;
import pro.taskana.task.api.exceptions.TaskCommentNotFoundException; import pro.taskana.task.api.exceptions.TaskCommentNotFoundException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; 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.Attachment;
import pro.taskana.task.api.models.ObjectReference; import pro.taskana.task.api.models.ObjectReference;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
@ -71,6 +74,7 @@ import pro.taskana.task.internal.models.TaskImpl;
import pro.taskana.task.internal.models.TaskSummaryImpl; import pro.taskana.task.internal.models.TaskSummaryImpl;
import pro.taskana.workbasket.api.WorkbasketPermission; import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService; import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException; import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
import pro.taskana.workbasket.api.models.WorkbasketSummary; import pro.taskana.workbasket.api.models.WorkbasketSummary;
@ -193,9 +197,7 @@ public class TaskServiceImpl implements TaskService {
} }
if (workbasket.isMarkedForDeletion()) { if (workbasket.isMarkedForDeletion()) {
throw new WorkbasketNotFoundException( throw new WorkbasketNotFoundException(workbasket.getId());
workbasket.getId(),
"The workbasket " + workbasket.getId() + " was marked for deletion");
} }
task.setWorkbasketSummary(workbasket.asSummary()); task.setWorkbasketSummary(workbasket.asSummary());
@ -260,8 +262,7 @@ public class TaskServiceImpl implements TaskService {
if (msg != null if (msg != null
&& (msg.contains("violation") || msg.contains("violates") || msg.contains("verletzt")) && (msg.contains("violation") || msg.contains("violates") || msg.contains("verletzt"))
&& msg.contains("external_id")) { && msg.contains("external_id")) {
throw new TaskAlreadyExistException( throw new TaskAlreadyExistException(task.getExternalId());
"Task with external id " + task.getExternalId() + " already exists");
} else { } else {
throw e; throw e;
} }
@ -274,7 +275,7 @@ public class TaskServiceImpl implements TaskService {
@Override @Override
public Task getTask(String id) throws NotAuthorizedException, TaskNotFoundException { public Task getTask(String id) throws NotAuthorizedException, TaskNotFoundException {
TaskImpl resultTask = null; TaskImpl resultTask;
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
@ -285,13 +286,10 @@ public class TaskServiceImpl implements TaskService {
String workbasketId = resultTask.getWorkbasketSummary().getId(); String workbasketId = resultTask.getWorkbasketSummary().getId();
List<WorkbasketSummary> workbaskets = query.idIn(workbasketId).list(); List<WorkbasketSummary> workbaskets = query.idIn(workbasketId).list();
if (workbaskets.isEmpty()) { if (workbaskets.isEmpty()) {
String currentUser = taskanaEngine.getEngine().getCurrentUserContext().getUserid(); throw new MismatchedWorkbasketPermissionException(
throw new NotAuthorizedException( taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
"The current user " workbasketId,
+ currentUser WorkbasketPermission.READ);
+ " has no read permission for workbasket "
+ workbasketId,
taskanaEngine.getEngine().getCurrentUserContext().getUserid());
} else { } else {
resultTask.setWorkbasketSummary(workbaskets.get(0)); resultTask.setWorkbasketSummary(workbaskets.get(0));
} }
@ -322,7 +320,7 @@ public class TaskServiceImpl implements TaskService {
resultTask.setClassificationSummary(classification); resultTask.setClassificationSummary(classification);
return resultTask; return resultTask;
} else { } else {
throw new TaskNotFoundException(id, String.format("Task with id %s was not found.", id)); throw new TaskNotFoundException(id);
} }
} finally { } finally {
taskanaEngine.returnConnection(); taskanaEngine.returnConnection();
@ -346,7 +344,7 @@ public class TaskServiceImpl implements TaskService {
@Override @Override
public Task setTaskRead(String taskId, boolean isRead) public Task setTaskRead(String taskId, boolean isRead)
throws TaskNotFoundException, NotAuthorizedException { throws TaskNotFoundException, NotAuthorizedException {
TaskImpl task = null; TaskImpl task;
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
task = (TaskImpl) getTask(taskId); task = (TaskImpl) getTask(taskId);
@ -692,43 +690,35 @@ public class TaskServiceImpl implements TaskService {
@Override @Override
public BulkOperationResults<String, TaskanaException> setOwnerOfTasks( public BulkOperationResults<String, TaskanaException> setOwnerOfTasks(
String owner, List<String> argTaskIds) { String owner, List<String> taskIds) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>(); BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
if (argTaskIds == null || argTaskIds.isEmpty()) { if (taskIds == null || taskIds.isEmpty()) {
return bulkLog; return bulkLog;
} }
// remove duplicates
List<String> taskIds = argTaskIds.stream().distinct().collect(Collectors.toList());
final int requestSize = taskIds.size();
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
// use only elements we are authorized for Pair<List<MinimalTaskSummary>, BulkLog> existingAndAuthorizedTasks =
Pair<List<MinimalTaskSummary>, BulkLog> resultsPair = getMinimalTaskSummaries(taskIds); getMinimalTaskSummaries(taskIds);
// set the Owner of these tasks we are authorized for bulkLog.addAllErrors(existingAndAuthorizedTasks.getRight());
List<MinimalTaskSummary> existingMinimalTaskSummaries = resultsPair.getLeft(); Pair<List<String>, BulkLog> taskIdsToUpdate =
taskIds = filterOutTasksWhichAreNotReady(existingAndAuthorizedTasks.getLeft());
existingMinimalTaskSummaries.stream() bulkLog.addAllErrors(taskIdsToUpdate.getRight());
.map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toList()); if (!taskIdsToUpdate.getLeft().isEmpty()) {
bulkLog.addAllErrors(resultsPair.getRight()); taskMapper.setOwnerOfTasks(owner, taskIdsToUpdate.getLeft(), Instant.now());
if (!taskIds.isEmpty()) {
final int numberOfAffectedTasks = taskMapper.setOwnerOfTasks(owner, taskIds, Instant.now());
if (numberOfAffectedTasks != taskIds.size()) { // all tasks were updated
// check the outcome
existingMinimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(
addExceptionsForTasksWhoseOwnerWasNotSet(owner, existingMinimalTaskSummaries));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Received the Request to set owner on {} tasks, actually modified tasks = {}"
+ ", could not set owner on {} tasks.",
requestSize,
numberOfAffectedTasks,
bulkLog.getFailedIds().size());
}
}
} }
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Received the Request to set owner on {} tasks, actually modified tasks = {}"
+ ", could not set owner on {} tasks.",
taskIds.size(),
taskIdsToUpdate.getLeft().size(),
bulkLog.getFailedIds().size());
}
return bulkLog; return bulkLog;
} finally { } finally {
taskanaEngine.returnConnection(); taskanaEngine.returnConnection();
@ -874,10 +864,10 @@ public class TaskServiceImpl implements TaskService {
} }
} }
Pair<List<MinimalTaskSummary>, BulkLog> getMinimalTaskSummaries(List<String> argTaskIds) { Pair<List<MinimalTaskSummary>, BulkLog> getMinimalTaskSummaries(Collection<String> argTaskIds) {
BulkLog bulkLog = new BulkLog(); BulkLog bulkLog = new BulkLog();
// remove duplicates // remove duplicates
List<String> taskIds = argTaskIds.stream().distinct().collect(Collectors.toList()); Set<String> taskIds = new HashSet<>(argTaskIds);
// get existing tasks // get existing tasks
List<MinimalTaskSummary> minimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null); List<MinimalTaskSummary> minimalTaskSummaries = taskMapper.findExistingTasks(taskIds, null);
bulkLog.addAllErrors(addExceptionsForNonExistingTasksToBulkLog(taskIds, minimalTaskSummaries)); bulkLog.addAllErrors(addExceptionsForNonExistingTasksToBulkLog(taskIds, minimalTaskSummaries));
@ -894,39 +884,40 @@ public class TaskServiceImpl implements TaskService {
if (taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN)) { if (taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN, TaskanaRole.TASK_ADMIN)) {
return Pair.of(existingTasks, bulkLog); return Pair.of(existingTasks, bulkLog);
} else { } else {
List<String> taskIds =
existingTasks.stream().map(MinimalTaskSummary::getTaskId).collect(Collectors.toList());
List<String> accessIds = taskanaEngine.getEngine().getCurrentUserContext().getAccessIds(); List<String> accessIds = taskanaEngine.getEngine().getCurrentUserContext().getAccessIds();
List<String> taskIdsNotAuthorizedFor = List<Pair<String, String>> taskAndWorkbasketIdsNotAuthorizedFor =
taskMapper.filterTaskIdsNotAuthorizedFor(taskIds, accessIds); taskMapper.getTaskAndWorkbasketIdsNotAuthorizedFor(existingTasks, accessIds);
String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid(); String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
for (String taskId : taskIdsNotAuthorizedFor) {
for (Pair<String, String> taskAndWorkbasketIds : taskAndWorkbasketIdsNotAuthorizedFor) {
bulkLog.addError( bulkLog.addError(
taskId, taskAndWorkbasketIds.getLeft(),
new NotAuthorizedException( new MismatchedWorkbasketPermissionException(
String.format("User %s is not authorized for task %s ", userId, taskId), userId)); userId, taskAndWorkbasketIds.getRight(), WorkbasketPermission.READ));
} }
taskIds.removeAll(taskIdsNotAuthorizedFor);
Set<String> taskIdsToRemove =
taskAndWorkbasketIdsNotAuthorizedFor.stream()
.map(Pair::getLeft)
.collect(Collectors.toSet());
List<MinimalTaskSummary> tasksAuthorizedFor = List<MinimalTaskSummary> tasksAuthorizedFor =
existingTasks.stream() existingTasks.stream()
.filter(t -> taskIds.contains(t.getTaskId())) .filter(t -> !taskIdsToRemove.contains(t.getTaskId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
return Pair.of(tasksAuthorizedFor, bulkLog); return Pair.of(tasksAuthorizedFor, bulkLog);
} }
} }
BulkLog addExceptionsForNonExistingTasksToBulkLog( BulkLog addExceptionsForNonExistingTasksToBulkLog(
List<String> requestTaskIds, List<MinimalTaskSummary> existingMinimalTaskSummaries) { Collection<String> requestTaskIds, List<MinimalTaskSummary> existingMinimalTaskSummaries) {
BulkLog bulkLog = new BulkLog(); BulkLog bulkLog = new BulkLog();
List<String> nonExistingTaskIds = new ArrayList<>(requestTaskIds); Set<String> existingTaskIds =
List<String> existingTaskIds =
existingMinimalTaskSummaries.stream() existingMinimalTaskSummaries.stream()
.map(MinimalTaskSummary::getTaskId) .map(MinimalTaskSummary::getTaskId)
.collect(Collectors.toList()); .collect(Collectors.toSet());
nonExistingTaskIds.removeAll(existingTaskIds); requestTaskIds.stream()
nonExistingTaskIds.forEach( .filter(taskId -> !existingTaskIds.contains(taskId))
taskId -> .forEach(taskId -> bulkLog.addError(taskId, new TaskNotFoundException(taskId)));
bulkLog.addError(taskId, new TaskNotFoundException(taskId, "Task was not found")));
return bulkLog; return bulkLog;
} }
@ -940,6 +931,24 @@ public class TaskServiceImpl implements TaskService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private Pair<List<String>, BulkLog> filterOutTasksWhichAreNotReady(
Collection<MinimalTaskSummary> minimalTaskSummaries) {
List<String> filteredTasks = new ArrayList<>(minimalTaskSummaries.size());
BulkLog bulkLog = new BulkLog();
for (MinimalTaskSummary taskSummary : minimalTaskSummaries) {
if (taskSummary.getTaskState() != TaskState.READY) {
bulkLog.addError(
taskSummary.getTaskId(),
new InvalidTaskStateException(
taskSummary.getTaskId(), taskSummary.getTaskState(), TaskState.READY));
} else {
filteredTasks.add(taskSummary.getTaskId());
}
}
return Pair.of(filteredTasks, bulkLog);
}
private List<TaskSummaryImpl> augmentTaskSummariesByContainedSummariesWithoutPartitioning( private List<TaskSummaryImpl> augmentTaskSummariesByContainedSummariesWithoutPartitioning(
List<TaskSummaryImpl> taskSummaries) { List<TaskSummaryImpl> taskSummaries) {
List<String> taskIds = List<String> taskIds =
@ -1021,10 +1030,7 @@ public class TaskServiceImpl implements TaskService {
pair -> { pair -> {
if (pair.getRight() == null) { if (pair.getRight() == null) {
String taskId = pair.getLeft(); String taskId = pair.getLeft();
bulkLog.addError( bulkLog.addError(taskId, new TaskNotFoundException(taskId));
taskId,
new TaskNotFoundException(
taskId, String.format("Task with id %s was not found.", taskId)));
return false; return false;
} }
return true; return true;
@ -1056,7 +1062,7 @@ public class TaskServiceImpl implements TaskService {
&& !oldTaskImpl.getClaimed().equals(newTaskImpl.getClaimed()) && !oldTaskImpl.getClaimed().equals(newTaskImpl.getClaimed())
|| oldTaskImpl.getState() != null || oldTaskImpl.getState() != null
&& !oldTaskImpl.getState().equals(newTaskImpl.getState())) { && !oldTaskImpl.getState().equals(newTaskImpl.getState())) {
throw new ConcurrencyException("The task has already been updated by another user"); throw new ConcurrencyException();
} }
newTaskImpl.setModified(Instant.now()); newTaskImpl.setModified(Instant.now());
} }
@ -1064,14 +1070,12 @@ public class TaskServiceImpl implements TaskService {
private TaskImpl terminateCancelCommonActions(String taskId, TaskState targetState) private TaskImpl terminateCancelCommonActions(String taskId, TaskState targetState)
throws NotAuthorizedException, TaskNotFoundException, InvalidStateException { throws NotAuthorizedException, TaskNotFoundException, InvalidStateException {
if (taskId == null || taskId.isEmpty()) { if (taskId == null || taskId.isEmpty()) {
throw new TaskNotFoundException( throw new TaskNotFoundException(taskId);
taskId, String.format("Task with id %s was not found.", taskId));
} }
TaskImpl task = (TaskImpl) getTask(taskId); TaskImpl task = (TaskImpl) getTask(taskId);
TaskState state = task.getState(); TaskState state = task.getState();
if (state.isEndState()) { if (state.isEndState()) {
throw new InvalidStateException( throw new InvalidTaskStateException(taskId, state, TaskState.READY);
String.format("Task with Id %s is already in an end state.", taskId));
} }
Instant now = Instant.now(); Instant now = Instant.now();
@ -1088,30 +1092,6 @@ public class TaskServiceImpl implements TaskService {
return task; return task;
} }
private BulkOperationResults<String, TaskanaException> addExceptionsForTasksWhoseOwnerWasNotSet(
String owner, List<MinimalTaskSummary> existingMinimalTaskSummaries) {
BulkOperationResults<String, TaskanaException> bulkLog = new BulkOperationResults<>();
for (MinimalTaskSummary taskSummary : existingMinimalTaskSummaries) {
if (!owner.equals(taskSummary.getOwner())) { // owner was not set
if (!TaskState.READY.equals(taskSummary.getTaskState())) { // due to invalid state
bulkLog.addError(
taskSummary.getTaskId(),
new InvalidStateException(
String.format(
"Task with id %s is in state %s and not in state ready.",
taskSummary.getTaskId(), taskSummary.getTaskState())));
} else { // due to unknown reason
bulkLog.addError(
taskSummary.getTaskId(),
new UpdateFailedException(
String.format("Could not set owner of Task %s .", taskSummary.getTaskId())));
}
}
}
return bulkLog;
}
private Task claim(String taskId, boolean forceClaim) private Task claim(String taskId, boolean forceClaim)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException { NotAuthorizedException {
@ -1159,16 +1139,14 @@ public class TaskServiceImpl implements TaskService {
private void checkPreconditionsForClaimTask(TaskSummary task, boolean forced) private void checkPreconditionsForClaimTask(TaskSummary task, boolean forced)
throws InvalidStateException, InvalidOwnerException { throws InvalidStateException, InvalidOwnerException {
TaskState state = task.getState(); TaskState state = task.getState();
if (!state.in(TaskState.READY, TaskState.CLAIMED)) { if (state.isEndState()) {
throw new InvalidStateException( throw new InvalidTaskStateException(
String.format("Task with Id %s is already in an end state.", task.getId())); task.getId(), task.getState(), EnumUtil.allValuesExceptFor(TaskState.END_STATES));
} }
if (!forced
&& state == TaskState.CLAIMED String userId = taskanaEngine.getEngine().getCurrentUserContext().getUserid();
&& !task.getOwner().equals(taskanaEngine.getEngine().getCurrentUserContext().getUserid())) { if (!forced && state == TaskState.CLAIMED && !task.getOwner().equals(userId)) {
throw new InvalidOwnerException( throw new InvalidOwnerException(userId, task.getId());
String.format(
"Task with id %s is already claimed by %s.", task.getId(), task.getOwner()));
} }
} }
@ -1179,17 +1157,17 @@ public class TaskServiceImpl implements TaskService {
private static void checkIfTaskIsTerminatedOrCancelled(TaskSummary task) private static void checkIfTaskIsTerminatedOrCancelled(TaskSummary task)
throws InvalidStateException { throws InvalidStateException {
if (task.getState().in(TaskState.CANCELLED, TaskState.TERMINATED)) { if (task.getState().in(TaskState.CANCELLED, TaskState.TERMINATED)) {
throw new InvalidStateException( throw new InvalidTaskStateException(
String.format( task.getId(),
"Cannot complete task %s because it is in state %s.", task.getId(), task.getState())); task.getState(),
EnumUtil.allValuesExceptFor(TaskState.CANCELLED, TaskState.TERMINATED));
} }
} }
private void checkPreconditionsForCompleteTask(TaskSummary task) private void checkPreconditionsForCompleteTask(TaskSummary task)
throws InvalidStateException, InvalidOwnerException { throws InvalidStateException, InvalidOwnerException {
if (taskIsNotClaimed(task)) { if (taskIsNotClaimed(task)) {
throw new InvalidStateException( throw new InvalidTaskStateException(task.getId(), task.getState(), TaskState.CLAIMED);
String.format("Task with Id %s has to be claimed before.", task.getId()));
} else if (!taskanaEngine } else if (!taskanaEngine
.getEngine() .getEngine()
.getCurrentUserContext() .getCurrentUserContext()
@ -1197,11 +1175,7 @@ public class TaskServiceImpl implements TaskService {
.contains(task.getOwner()) .contains(task.getOwner())
&& !taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN)) { && !taskanaEngine.getEngine().isUserInRole(TaskanaRole.ADMIN)) {
throw new InvalidOwnerException( throw new InvalidOwnerException(
String.format( taskanaEngine.getEngine().getCurrentUserContext().getUserid(), task.getId());
"Owner of task %s is %s, but current user is %s ",
task.getId(),
task.getOwner(),
taskanaEngine.getEngine().getCurrentUserContext().getUserid()));
} }
} }
@ -1215,12 +1189,11 @@ public class TaskServiceImpl implements TaskService {
task = (TaskImpl) getTask(taskId); task = (TaskImpl) getTask(taskId);
TaskState state = task.getState(); TaskState state = task.getState();
if (state.isEndState()) { if (state.isEndState()) {
throw new InvalidStateException( throw new InvalidTaskStateException(
String.format("Task with Id %s is already in an end state.", taskId)); taskId, state, EnumUtil.allValuesExceptFor(TaskState.END_STATES));
} }
if (state == TaskState.CLAIMED && !forceUnclaim && !userId.equals(task.getOwner())) { if (state == TaskState.CLAIMED && !forceUnclaim && !userId.equals(task.getOwner())) {
throw new InvalidOwnerException( throw new InvalidOwnerException(userId, taskId);
String.format("Task with id %s is already claimed by %s.", taskId, task.getOwner()));
} }
Instant now = Instant.now(); Instant now = Instant.now();
task.setOwner(null); task.setOwner(null);
@ -1294,15 +1267,14 @@ public class TaskServiceImpl implements TaskService {
task = (TaskImpl) getTask(taskId); task = (TaskImpl) getTask(taskId);
if (!(task.getState().isEndState()) && !forceDelete) { if (!(task.getState().isEndState()) && !forceDelete) {
throw new InvalidStateException( throw new InvalidTaskStateException(taskId, task.getState(), TaskState.END_STATES);
"Cannot delete Task " + taskId + " because it is not in an end state.");
} }
if ((!task.getState().in(TaskState.TERMINATED, TaskState.CANCELLED)) if ((!task.getState().in(TaskState.TERMINATED, TaskState.CANCELLED))
&& CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(task.getCallbackState())) { && CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(task.getCallbackState())) {
throw new InvalidStateException( throw new InvalidCallbackStateException(
String.format( taskId,
"Task wit Id %s cannot be deleted because its callback is not yet processed", task.getCallbackState(),
taskId)); EnumUtil.allValuesExceptFor(CallbackState.CALLBACK_PROCESSING_REQUIRED));
} }
attachmentMapper.deleteMultipleByTaskIds(Collections.singletonList(taskId)); attachmentMapper.deleteMultipleByTaskIds(Collections.singletonList(taskId));
@ -1337,23 +1309,23 @@ public class TaskServiceImpl implements TaskService {
.findFirst() .findFirst()
.orElse(null); .orElse(null);
if (foundSummary == null) { if (foundSummary == null) {
bulkLog.addError( bulkLog.addError(currentTaskId, new TaskNotFoundException(currentTaskId));
currentTaskId,
new TaskNotFoundException(
currentTaskId, String.format("Task with id %s was not found.", currentTaskId)));
taskIdIterator.remove(); taskIdIterator.remove();
} else if (!(foundSummary.getTaskState().isEndState())) { } else if (!(foundSummary.getTaskState().isEndState())) {
bulkLog.addError(currentTaskId, new InvalidStateException(currentTaskId)); bulkLog.addError(
currentTaskId,
new InvalidTaskStateException(
currentTaskId, foundSummary.getTaskState(), TaskState.END_STATES));
taskIdIterator.remove(); taskIdIterator.remove();
} else { } else {
if ((!foundSummary.getTaskState().in(TaskState.CANCELLED, TaskState.TERMINATED)) if ((!foundSummary.getTaskState().in(TaskState.CANCELLED, TaskState.TERMINATED))
&& CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(foundSummary.getCallbackState())) { && CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(foundSummary.getCallbackState())) {
bulkLog.addError( bulkLog.addError(
currentTaskId, currentTaskId,
new InvalidStateException( new InvalidCallbackStateException(
String.format( currentTaskId,
"Task wit Id %s cannot be deleted because its callback is not yet processed", foundSummary.getCallbackState(),
currentTaskId))); EnumUtil.allValuesExceptFor(CallbackState.CALLBACK_PROCESSING_REQUIRED)));
taskIdIterator.remove(); taskIdIterator.remove();
} }
} }
@ -1376,23 +1348,20 @@ public class TaskServiceImpl implements TaskService {
.filter(taskSummary -> currentExternalId.equals(taskSummary.getExternalId())) .filter(taskSummary -> currentExternalId.equals(taskSummary.getExternalId()))
.findFirst(); .findFirst();
if (foundSummary.isPresent()) { if (foundSummary.isPresent()) {
if (!desiredCallbackStateCanBeSetForFoundSummary( Optional<TaskanaException> invalidStateException =
foundSummary.get(), desiredCallbackState)) { desiredCallbackStateCanBeSetForFoundSummary(foundSummary.get(), desiredCallbackState);
bulkLog.addError(currentExternalId, new InvalidStateException(currentExternalId)); if (invalidStateException.isPresent()) {
bulkLog.addError(currentExternalId, invalidStateException.get());
externalIdIterator.remove(); externalIdIterator.remove();
} }
} else { } else {
bulkLog.addError( bulkLog.addError(currentExternalId, new TaskNotFoundException(currentExternalId));
currentExternalId,
new TaskNotFoundException(
currentExternalId,
String.format("Task with id %s was not found.", currentExternalId)));
externalIdIterator.remove(); externalIdIterator.remove();
} }
} }
} }
private boolean desiredCallbackStateCanBeSetForFoundSummary( private Optional<TaskanaException> desiredCallbackStateCanBeSetForFoundSummary(
MinimalTaskSummary foundSummary, CallbackState desiredCallbackState) { MinimalTaskSummary foundSummary, CallbackState desiredCallbackState) {
CallbackState currentTaskCallbackState = foundSummary.getCallbackState(); CallbackState currentTaskCallbackState = foundSummary.getCallbackState();
@ -1400,21 +1369,48 @@ public class TaskServiceImpl implements TaskService {
switch (desiredCallbackState) { switch (desiredCallbackState) {
case CALLBACK_PROCESSING_COMPLETED: case CALLBACK_PROCESSING_COMPLETED:
return currentTaskState.isEndState(); if (!currentTaskState.isEndState()) {
return Optional.of(
new InvalidTaskStateException(
foundSummary.getTaskId(), foundSummary.getTaskState(), TaskState.END_STATES));
}
break;
case CLAIMED: case CLAIMED:
if (!currentTaskState.equals(TaskState.CLAIMED)) { if (!currentTaskState.equals(TaskState.CLAIMED)) {
return false; return Optional.of(
} else { new InvalidTaskStateException(
return currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_REQUIRED); foundSummary.getTaskId(), foundSummary.getTaskState(), TaskState.CLAIMED));
} }
if (!currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_REQUIRED)) {
return Optional.of(
new InvalidCallbackStateException(
foundSummary.getTaskId(),
currentTaskCallbackState,
CallbackState.CALLBACK_PROCESSING_REQUIRED));
}
break;
case CALLBACK_PROCESSING_REQUIRED: case CALLBACK_PROCESSING_REQUIRED:
return !currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_COMPLETED); if (currentTaskCallbackState.equals(CallbackState.CALLBACK_PROCESSING_COMPLETED)) {
return Optional.of(
new InvalidCallbackStateException(
foundSummary.getTaskId(),
currentTaskCallbackState,
EnumUtil.allValuesExceptFor(CallbackState.CALLBACK_PROCESSING_COMPLETED)));
}
break;
default: default:
return false; return Optional.of(
new InvalidArgumentException(
String.format(
"desired callbackState has to be in '%s'",
Arrays.toString(
new CallbackState[] {
CallbackState.CALLBACK_PROCESSING_COMPLETED,
CallbackState.CLAIMED,
CallbackState.CALLBACK_PROCESSING_REQUIRED
}))));
} }
return Optional.empty();
} }
private void standardSettingsOnTaskCreation(TaskImpl task, Classification classification) private void standardSettingsOnTaskCreation(TaskImpl task, Classification classification)
@ -1773,10 +1769,8 @@ public class TaskServiceImpl implements TaskService {
// owner can only be changed if task is in state ready // owner can only be changed if task is in state ready
boolean isOwnerChanged = !Objects.equals(newTaskImpl1.getOwner(), oldTaskImpl.getOwner()); boolean isOwnerChanged = !Objects.equals(newTaskImpl1.getOwner(), oldTaskImpl.getOwner());
if (isOwnerChanged && oldTaskImpl.getState() != TaskState.READY) { if (isOwnerChanged && oldTaskImpl.getState() != TaskState.READY) {
throw new InvalidStateException( throw new InvalidTaskStateException(
String.format( oldTaskImpl.getId(), oldTaskImpl.getState(), TaskState.READY);
"Task with id %s is in state %s and not in state ready.",
oldTaskImpl.getId(), oldTaskImpl.getState()));
} }
} }

View File

@ -16,12 +16,14 @@ import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.util.EnumUtil;
import pro.taskana.common.internal.util.IdGenerator; import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.common.internal.util.ObjectAttributeChangeDetector; import pro.taskana.common.internal.util.ObjectAttributeChangeDetector;
import pro.taskana.spi.history.api.events.task.TaskTransferredEvent; import pro.taskana.spi.history.api.events.task.TaskTransferredEvent;
import pro.taskana.spi.history.internal.HistoryEventManager; import pro.taskana.spi.history.internal.HistoryEventManager;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.InvalidStateException; import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary; import pro.taskana.task.api.models.TaskSummary;
@ -29,6 +31,7 @@ import pro.taskana.task.internal.models.TaskImpl;
import pro.taskana.task.internal.models.TaskSummaryImpl; import pro.taskana.task.internal.models.TaskSummaryImpl;
import pro.taskana.workbasket.api.WorkbasketPermission; import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService; import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException; import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.WorkbasketSummary; import pro.taskana.workbasket.api.models.WorkbasketSummary;
import pro.taskana.workbasket.internal.WorkbasketQueryImpl; import pro.taskana.workbasket.internal.WorkbasketQueryImpl;
@ -154,8 +157,8 @@ final class TaskTransferrer {
Task task, WorkbasketSummary destinationWorkbasket, WorkbasketSummary originWorkbasket) Task task, WorkbasketSummary destinationWorkbasket, WorkbasketSummary originWorkbasket)
throws NotAuthorizedException, WorkbasketNotFoundException, InvalidStateException { throws NotAuthorizedException, WorkbasketNotFoundException, InvalidStateException {
if (task.getState().isEndState()) { if (task.getState().isEndState()) {
throw new InvalidStateException( throw new InvalidTaskStateException(
String.format("Task '%s' is in end state and cannot be transferred.", task.getId())); task.getId(), task.getState(), EnumUtil.allValuesExceptFor(TaskState.END_STATES));
} }
workbasketService.checkAuthorization(originWorkbasket.getId(), WorkbasketPermission.TRANSFER); workbasketService.checkAuthorization(originWorkbasket.getId(), WorkbasketPermission.TRANSFER);
checkDestinationWorkbasket(destinationWorkbasket); checkDestinationWorkbasket(destinationWorkbasket);
@ -167,9 +170,7 @@ final class TaskTransferrer {
destinationWorkbasket.getId(), WorkbasketPermission.APPEND); destinationWorkbasket.getId(), WorkbasketPermission.APPEND);
if (destinationWorkbasket.isMarkedForDeletion()) { if (destinationWorkbasket.isMarkedForDeletion()) {
throw new WorkbasketNotFoundException( throw new WorkbasketNotFoundException(destinationWorkbasket.getId());
destinationWorkbasket.getId(),
String.format("Workbasket '%s' was marked for deletion.", destinationWorkbasket.getId()));
} }
} }
@ -204,18 +205,17 @@ final class TaskTransferrer {
if (taskId == null || taskId.isEmpty()) { if (taskId == null || taskId.isEmpty()) {
error = new InvalidArgumentException("TaskId should not be null or empty"); error = new InvalidArgumentException("TaskId should not be null or empty");
} else if (taskSummary == null) { } else if (taskSummary == null) {
error = error = new TaskNotFoundException(taskId);
new TaskNotFoundException(
taskId, String.format("Task with id '%s' was not found.", taskId));
} else if (taskSummary.getState().isEndState()) { } else if (taskSummary.getState().isEndState()) {
error = error =
new InvalidStateException( new InvalidTaskStateException(
String.format("Task in end state with id '%s' cannot be transferred.", taskId)); taskId, taskSummary.getState(), EnumUtil.allValuesExceptFor(TaskState.END_STATES));
} else if (!sourceWorkbasketIds.contains(taskSummary.getWorkbasketSummary().getId())) { } else if (!sourceWorkbasketIds.contains(taskSummary.getWorkbasketSummary().getId())) {
error = error =
new NotAuthorizedException( new MismatchedWorkbasketPermissionException(
"The workbasket of this task got not TRANSFER permissions. TaskId=" + taskId, taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
taskanaEngine.getEngine().getCurrentUserContext().getUserid()); taskSummary.getWorkbasketSummary().getId(),
WorkbasketPermission.TRANSFER);
} }
return Optional.ofNullable(error); return Optional.ofNullable(error);
} }

View File

@ -17,6 +17,7 @@ import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.TimeInterval; import pro.taskana.common.api.TimeInterval;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.JobServiceImpl; import pro.taskana.common.internal.JobServiceImpl;
import pro.taskana.common.internal.jobs.AbstractTaskanaJob; import pro.taskana.common.internal.jobs.AbstractTaskanaJob;
@ -62,7 +63,7 @@ public class TaskCleanupJob extends AbstractTaskanaJob {
LOGGER.info("Job ended successfully. {} tasks deleted.", totalNumberOfTasksDeleted); LOGGER.info("Job ended successfully. {} tasks deleted.", totalNumberOfTasksDeleted);
} catch (Exception e) { } catch (Exception e) {
throw new TaskanaException("Error while processing TaskCleanupJob.", e); throw new SystemException("Error while processing TaskCleanupJob.", e);
} finally { } finally {
scheduleNextCleanupJob(); scheduleNextCleanupJob();
} }

View File

@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import pro.taskana.common.api.ScheduledJob; import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.jobs.AbstractTaskanaJob; import pro.taskana.common.internal.jobs.AbstractTaskanaJob;
import pro.taskana.common.internal.transaction.TaskanaTransactionProvider; import pro.taskana.common.internal.transaction.TaskanaTransactionProvider;
@ -43,7 +44,7 @@ public class TaskRefreshJob extends AbstractTaskanaJob {
affectedTaskIds, serviceLevelChanged, priorityChanged); affectedTaskIds, serviceLevelChanged, priorityChanged);
LOGGER.info("TaskRefreshJob ended successfully."); LOGGER.info("TaskRefreshJob ended successfully.");
} catch (Exception e) { } catch (Exception e) {
throw new TaskanaException("Error while processing TaskRefreshJob.", e); throw new SystemException("Error while processing TaskRefreshJob.", e);
} }
} }

View File

@ -8,7 +8,6 @@ import pro.taskana.common.api.exceptions.DomainNotFoundException;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.workbasket.api.exceptions.InvalidWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketInUseException; import pro.taskana.workbasket.api.exceptions.WorkbasketInUseException;
@ -56,14 +55,14 @@ public interface WorkbasketService {
* *
* @param workbasket The Workbasket to create * @param workbasket The Workbasket to create
* @return the created and inserted Workbasket * @return the created and inserted Workbasket
* @throws InvalidWorkbasketException If a required property of the Workbasket is not set. * @throws InvalidArgumentException If a required property of the Workbasket is not set.
* @throws NotAuthorizedException if the current user is not member of role BUSINESS_ADMIN or * @throws NotAuthorizedException if the current user is not member of role BUSINESS_ADMIN or
* ADMIN * ADMIN
* @throws WorkbasketAlreadyExistException if the Workbasket exists already * @throws WorkbasketAlreadyExistException if the Workbasket exists already
* @throws DomainNotFoundException if the domain does not exist in the configuration. * @throws DomainNotFoundException if the domain does not exist in the configuration.
*/ */
Workbasket createWorkbasket(Workbasket workbasket) Workbasket createWorkbasket(Workbasket workbasket)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketAlreadyExistException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketAlreadyExistException,
DomainNotFoundException; DomainNotFoundException;
/** /**
@ -71,20 +70,21 @@ public interface WorkbasketService {
* *
* @param workbasket The Workbasket to update * @param workbasket The Workbasket to update
* @return the updated Workbasket * @return the updated Workbasket
* @throws InvalidWorkbasketException if workbasket name or type is invalid * @throws InvalidArgumentException if workbasket name or type is invalid
* @throws NotAuthorizedException if the current user is not authorized to update the work basket * @throws NotAuthorizedException if the current user is not authorized to update the {@linkplain
* @throws WorkbasketNotFoundException if the workbasket cannot be found. * Workbasket}
* @throws ConcurrencyException if an attempt is made to update the workbasket and another user * @throws WorkbasketNotFoundException if the {@linkplain Workbasket} cannot be found.
* updated it already * @throws ConcurrencyException if an attempt is made to update the {@linkplain Workbasket} and
* another user updated it already
*/ */
Workbasket updateWorkbasket(Workbasket workbasket) Workbasket updateWorkbasket(Workbasket workbasket)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketNotFoundException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketNotFoundException,
ConcurrencyException; ConcurrencyException;
/** /**
* Returns a new WorkbasketAccessItem which is not inserted. * Returns a new WorkbasketAccessItem which is not inserted.
* *
* @param workbasketId the workbasket id used to identify the referenced workbasket * @param workbasketId the workbasket id used to identify the referenced {@linkplain Workbasket}
* @param accessId the group id or user id for which access is controlled * @param accessId the group id or user id for which access is controlled
* @return new WorkbasketAccessItem * @return new WorkbasketAccessItem
*/ */
@ -138,7 +138,8 @@ public interface WorkbasketService {
* specified, the current user needs all of them. * specified, the current user needs all of them.
* @throws NotAuthorizedException if the current user has not the requested authorization for the * @throws NotAuthorizedException if the current user has not the requested authorization for the
* specified workbasket * specified workbasket
* @throws WorkbasketNotFoundException if the workbasket cannot be found for the given ID. * @throws WorkbasketNotFoundException if the {@linkplain Workbasket} cannot be found for the
* given {@linkplain Workbasket#getId() id}.
*/ */
void checkAuthorization(String workbasketId, WorkbasketPermission... permission) void checkAuthorization(String workbasketId, WorkbasketPermission... permission)
throws NotAuthorizedException, WorkbasketNotFoundException; throws NotAuthorizedException, WorkbasketNotFoundException;
@ -159,21 +160,24 @@ public interface WorkbasketService {
throws NotAuthorizedException, WorkbasketNotFoundException; throws NotAuthorizedException, WorkbasketNotFoundException;
/** /**
* Get all {@link WorkbasketAccessItem s} for a Workbasket. * Get all {@link WorkbasketAccessItem s} for a {@linkplain Workbasket}.
* *
* @param workbasketId the id of the Workbasket * @param workbasketId the {@linkplain Workbasket#getId() id} of the {@linkplain Workbasket}
* @return List of WorkbasketAccessItems for the Workbasket with workbasketKey * @return List of {@linkplain WorkbasketAccessItem}s for the {@linkplain Workbasket}
* @throws NotAuthorizedException if the current user is not member of role BUSINESS_ADMIN or * @throws NotAuthorizedException if the current user is not member of role {@linkplain
* ADMIN * pro.taskana.common.api.TaskanaRole#BUSINESS_ADMIN} or {@linkplain
* pro.taskana.common.api.TaskanaRole#ADMIN}
* @throws WorkbasketNotFoundException if the {@linkplain Workbasket} cannot be found for the
* given {@linkplain Workbasket#getId() id}.
*/ */
List<WorkbasketAccessItem> getWorkbasketAccessItems(String workbasketId) List<WorkbasketAccessItem> getWorkbasketAccessItems(String workbasketId)
throws NotAuthorizedException; throws NotAuthorizedException, WorkbasketNotFoundException;
/** /**
* Setting up the new WorkbasketAccessItems for a Workbasket. Already stored values will be * Setting up the new WorkbasketAccessItems for a Workbasket. Already stored values will be
* completely replaced by the current ones. * completely replaced by the current ones.
* *
* <p>Preconditions for each {@link WorkbasketAccessItem} in {@code wbAccessItems}: * <p>Preconditions for each {@link WorkbasketAccessItem} then {@code wbAccessItems}:
* *
* <ul> * <ul>
* <li>{@link WorkbasketAccessItem#getWorkbasketId()} is not null * <li>{@link WorkbasketAccessItem#getWorkbasketId()} is not null
@ -189,10 +193,12 @@ public interface WorkbasketService {
* ADMIN * ADMIN
* @throws WorkbasketAccessItemAlreadyExistException if {@code wbAccessItems} contains multiple * @throws WorkbasketAccessItemAlreadyExistException if {@code wbAccessItems} contains multiple
* accessItems with the same accessId. * accessItems with the same accessId.
* @throws WorkbasketNotFoundException if the {@linkplain Workbasket} cannot be found for the
* given {@linkplain Workbasket#getId() id}.
*/ */
void setWorkbasketAccessItems(String workbasketId, List<WorkbasketAccessItem> wbAccessItems) void setWorkbasketAccessItems(String workbasketId, List<WorkbasketAccessItem> wbAccessItems)
throws InvalidArgumentException, NotAuthorizedException, throws InvalidArgumentException, NotAuthorizedException,
WorkbasketAccessItemAlreadyExistException; WorkbasketAccessItemAlreadyExistException, WorkbasketNotFoundException;
/** /**
* This method provides a query builder for querying the database. * This method provides a query builder for querying the database.

View File

@ -1,14 +0,0 @@
package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.TaskanaException;
/**
* This exception is thrown when a request is made to insert or update a workbasket that is missing
* a required property.
*/
public class InvalidWorkbasketException extends TaskanaException {
public InvalidWorkbasketException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,96 @@
package pro.taskana.workbasket.api.exceptions;
import java.util.Arrays;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.models.Workbasket;
/**
* This exception is thrown when the current user does not have a certain {@linkplain
* WorkbasketPermission permission} on a {@linkplain Workbasket}.
*/
public class MismatchedWorkbasketPermissionException extends NotAuthorizedException {
public static final String ERROR_KEY_KEY_DOMAIN = "WORKBASKET_WITH_KEY_MISMATCHED_PERMISSION";
public static final String ERROR_KEY_ID = "WORKBASKET_WITH_ID_MISMATCHED_PERMISSION";
private final String currentUserId;
private final WorkbasketPermission[] requiredPermissions;
private final String workbasketId;
private final String workbasketKey;
private final String domain;
public MismatchedWorkbasketPermissionException(
String currentUserId, String workbasketId, WorkbasketPermission... requiredPermissions) {
super(
String.format(
"Not authorized. The current user '%s' has no '%s' permission(s) for Workbasket '%s'.",
currentUserId, Arrays.toString(requiredPermissions), workbasketId),
ErrorCode.of(
ERROR_KEY_ID,
MapCreator.of(
"currentUserId",
currentUserId,
"workbasketId",
workbasketId,
"requiredPermissions",
requiredPermissions)));
this.currentUserId = currentUserId;
this.requiredPermissions = requiredPermissions;
this.workbasketId = workbasketId;
workbasketKey = null;
domain = null;
}
public MismatchedWorkbasketPermissionException(
String currentUserId,
String workbasketKey,
String domain,
WorkbasketPermission... requiredPermissions) {
super(
String.format(
"Not authorized. The current user '%s' has no '%s' permission for "
+ "Workbasket with key '%s' in domain '%s'.",
currentUserId, Arrays.toString(requiredPermissions), workbasketKey, domain),
ErrorCode.of(
ERROR_KEY_KEY_DOMAIN,
MapCreator.of(
"currentUserId",
currentUserId,
"workbasketKey",
workbasketKey,
"domain",
domain,
"requiredPermissions",
requiredPermissions)));
this.currentUserId = currentUserId;
this.requiredPermissions = requiredPermissions;
this.workbasketKey = workbasketKey;
this.domain = domain;
workbasketId = null;
}
public String getWorkbasketKey() {
return workbasketKey;
}
public String getDomain() {
return domain;
}
public String getWorkbasketId() {
return workbasketId;
}
public WorkbasketPermission[] getRequiredPermissions() {
return requiredPermissions;
}
public String getCurrentUserId() {
return currentUserId;
}
}

View File

@ -1,15 +1,14 @@
package pro.taskana.workbasket.api.exceptions; package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException; import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.workbasket.api.models.Workbasket;
/** This exception is used to communicate that a user is not authorized to query a Workbasket. */ /** This exception is thrown when a user is not authorized to query a {@linkplain Workbasket}. */
public class NotAuthorizedToQueryWorkbasketException extends TaskanaRuntimeException { public class NotAuthorizedToQueryWorkbasketException extends TaskanaRuntimeException {
public NotAuthorizedToQueryWorkbasketException(String msg) { public NotAuthorizedToQueryWorkbasketException(
super(msg); String message, ErrorCode errorCode, Throwable cause) {
} super(message, errorCode, cause);
public NotAuthorizedToQueryWorkbasketException(String msg, Throwable cause) {
super(msg, cause);
} }
} }

View File

@ -1,15 +1,35 @@
package pro.taskana.workbasket.api.exceptions; package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.workbasket.api.models.WorkbasketAccessItem; import pro.taskana.workbasket.api.models.WorkbasketAccessItem;
/**
* This exception is thrown when an already existing {@linkplain WorkbasketAccessItem} was tried to
* be created.
*/
public class WorkbasketAccessItemAlreadyExistException extends TaskanaException { public class WorkbasketAccessItemAlreadyExistException extends TaskanaException {
public WorkbasketAccessItemAlreadyExistException(WorkbasketAccessItem accessItem) { public static final String ERROR_KEY = "WORKBASKET_ACCESS_ITEM_ALREADY_EXISTS";
private final String accessId;
private final String workbasketId;
public WorkbasketAccessItemAlreadyExistException(String accessId, String workbasketId) {
super( super(
String.format( String.format(
"WorkbasketAccessItem for accessId '%s' " "WorkbasketAccessItem with access id '%s' and workbasket id '%s' already exists.",
+ "and WorkbasketId '%s', WorkbasketKey '%s' exists already.", accessId, workbasketId),
accessItem.getAccessId(), accessItem.getWorkbasketId(), accessItem.getWorkbasketKey())); ErrorCode.of(ERROR_KEY, MapCreator.of("accessId", accessId, "workbasketId", workbasketId)));
this.accessId = accessId;
this.workbasketId = workbasketId;
}
public String getAccessId() {
return accessId;
}
public String getWorkbasketId() {
return workbasketId;
} }
} }

View File

@ -1,17 +1,33 @@
package pro.taskana.workbasket.api.exceptions; package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
/** Thrown, when a workbasket does already exits, but wanted to create with same ID. */ /**
* This exception is thrown when an already existing {@linkplain Workbasket} was tried to be
* created.
*/
public class WorkbasketAlreadyExistException extends TaskanaException { public class WorkbasketAlreadyExistException extends TaskanaException {
public WorkbasketAlreadyExistException(Workbasket workbasket) { public static final String ERROR_KEY = "WORKBASKET_ALREADY_EXISTS";
private final String key;
private final String domain;
public WorkbasketAlreadyExistException(String key, String domain) {
super( super(
"A workbasket with key '" String.format("A Workbasket with key '%s' already exists in domain '%s'.", key, domain),
+ workbasket.getKey() ErrorCode.of(ERROR_KEY, MapCreator.of("workbasketKey", key, "domain", domain)));
+ "' already exists in domain '" this.key = key;
+ workbasket.getDomain() this.domain = domain;
+ "'."); }
public String getKey() {
return key;
}
public String getDomain() {
return domain;
} }
} }

View File

@ -1,11 +1,29 @@
package pro.taskana.workbasket.api.exceptions; package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.workbasket.api.models.Workbasket;
/** Thrown if a specific Workbasket does have content and should be deleted. */ /**
* This exception is thrown when a specific {@linkplain Workbasket} does have content and is tried
* to be deleted.
*/
public class WorkbasketInUseException extends TaskanaException { public class WorkbasketInUseException extends TaskanaException {
public WorkbasketInUseException(String msg) { public static final String ERROR_KEY = "WORKBASKET_IN_USE";
super(msg); private final String workbasketId;
public WorkbasketInUseException(String workbasketId) {
super(
String.format(
"Workbasket '%s' contains non-completed Tasks and can't be marked for deletion.",
workbasketId),
ErrorCode.of(ERROR_KEY, MapCreator.of("workbasketId", workbasketId)));
this.workbasketId = workbasketId;
}
public String getWorkbasketId() {
return workbasketId;
} }
} }

View File

@ -0,0 +1,29 @@
package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.workbasket.api.models.Workbasket;
/**
* This exception is thrown when a {@linkplain Workbasket}, which was {@linkplain
* Workbasket#isMarkedForDeletion() marked for deletion}, could not be deleted.
*/
public class WorkbasketMarkedForDeletionException extends TaskanaException {
public static final String ERROR_KEY = "WORKBASKET_MARKED_FOR_DELETION";
private final String workbasketId;
public WorkbasketMarkedForDeletionException(String workbasketId) {
super(
String.format(
"Workbasket with id '%s' could not be deleted, but was marked for deletion.",
workbasketId),
ErrorCode.of(ERROR_KEY, MapCreator.of("workbasketId", workbasketId)));
this.workbasketId = workbasketId;
}
public String getWorkbasketId() {
return workbasketId;
}
}

View File

@ -1,23 +1,41 @@
package pro.taskana.workbasket.api.exceptions; package pro.taskana.workbasket.api.exceptions;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.NotFoundException; import pro.taskana.common.api.exceptions.NotFoundException;
import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.workbasket.api.models.Workbasket;
/** This exception will be thrown if a specific workbasket is not in the database. */ /** This exception is thrown when a specific {@linkplain Workbasket} is not in the database. */
public class WorkbasketNotFoundException extends NotFoundException { public class WorkbasketNotFoundException extends NotFoundException {
private String key; public static final String ERROR_KEY_ID = "WORKBASKET_WITH_ID_NOT_FOUND";
private String domain; public static final String ERROR_KEY_KEY_DOMAIN = "WORKBASKET_WITH_KEY_NOT_FOUND";
private final String id;
private final String key;
private final String domain;
public WorkbasketNotFoundException(String id, String msg) { public WorkbasketNotFoundException(String id) {
super(id, msg); super(
String.format("Workbasket with id '%s' was not found.", id),
ErrorCode.of(ERROR_KEY_ID, MapCreator.of("workbasketId", id)));
this.id = id;
key = null;
domain = null;
} }
public WorkbasketNotFoundException(String key, String domain, String msg) { public WorkbasketNotFoundException(String key, String domain) {
super(null, msg); super(
String.format("Workbasket with key '%s' and domain '%s' was not found.", key, domain),
ErrorCode.of(ERROR_KEY_KEY_DOMAIN, MapCreator.of("workbasketKey", key, "domain", domain)));
id = null;
this.key = key; this.key = key;
this.domain = domain; this.domain = domain;
} }
public String getId() {
return id;
}
public String getKey() { public String getKey() {
return key; return key;
} }

View File

@ -6,6 +6,7 @@ import java.util.List;
import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException; import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.workbasket.api.AbstractWorkbasketAccessItemQuery; import pro.taskana.workbasket.api.AbstractWorkbasketAccessItemQuery;
@ -93,7 +94,7 @@ abstract class AbstractWorkbasketAccessItemQueryImpl<
} catch (PersistenceException e) { } catch (PersistenceException e) {
if (e.getMessage().contains("ERRORCODE=-4470")) { if (e.getMessage().contains("ERRORCODE=-4470")) {
TaskanaRuntimeException ex = TaskanaRuntimeException ex =
new TaskanaRuntimeException( new SystemException(
"The offset beginning was set over the amount of result-rows.", e.getCause()); "The offset beginning was set over the amount of result-rows.", e.getCause());
ex.setStackTrace(e.getStackTrace()); ex.setStackTrace(e.getStackTrace());
throw ex; throw ex;

View File

@ -6,6 +6,7 @@ import java.util.List;
import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException; import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.workbasket.api.AccessItemQueryColumnName; import pro.taskana.workbasket.api.AccessItemQueryColumnName;
@ -116,7 +117,7 @@ public class WorkbasketAccessItemQueryImpl implements WorkbasketAccessItemQuery
} catch (PersistenceException e) { } catch (PersistenceException e) {
if (e.getMessage().contains("ERRORCODE=-4470")) { if (e.getMessage().contains("ERRORCODE=-4470")) {
TaskanaRuntimeException ex = TaskanaRuntimeException ex =
new TaskanaRuntimeException( new SystemException(
"The offset beginning was set over the amount of result-rows.", e.getCause()); "The offset beginning was set over the amount of result-rows.", e.getCause());
ex.setStackTrace(e.getStackTrace()); ex.setStackTrace(e.getStackTrace());
throw ex; throw ex;

View File

@ -177,10 +177,10 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
.checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN, TaskanaRole.TASK_ADMIN); .checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN, TaskanaRole.TASK_ADMIN);
// Checking pre-conditions // Checking pre-conditions
if (permission == null) { if (permission == null) {
throw new InvalidArgumentException("Permission can´t be null."); throw new InvalidArgumentException("Permission can't be null.");
} }
if (accessIds == null || accessIds.length == 0) { if (accessIds == null || accessIds.length == 0) {
throw new InvalidArgumentException("accessIds can´t be NULL or empty."); throw new InvalidArgumentException("accessIds can't be NULL or empty.");
} }
// set up permissions and ids // set up permissions and ids
@ -376,7 +376,7 @@ public class WorkbasketQueryImpl implements WorkbasketQuery {
} catch (PersistenceException e) { } catch (PersistenceException e) {
if (e.getMessage().contains("ERRORCODE=-4470")) { if (e.getMessage().contains("ERRORCODE=-4470")) {
TaskanaRuntimeException ex = TaskanaRuntimeException ex =
new TaskanaRuntimeException( new SystemException(
"The offset beginning was set over the amount of result-rows.", e.getCause()); "The offset beginning was set over the amount of result-rows.", e.getCause());
ex.setStackTrace(e.getStackTrace()); ex.setStackTrace(e.getStackTrace());
throw ex; throw ex;

View File

@ -6,6 +6,7 @@ import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.exceptions.PersistenceException;
@ -40,10 +41,11 @@ import pro.taskana.workbasket.api.WorkbasketAccessItemQuery;
import pro.taskana.workbasket.api.WorkbasketPermission; import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketQuery; import pro.taskana.workbasket.api.WorkbasketQuery;
import pro.taskana.workbasket.api.WorkbasketService; import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.InvalidWorkbasketException; import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketInUseException; import pro.taskana.workbasket.api.exceptions.WorkbasketInUseException;
import pro.taskana.workbasket.api.exceptions.WorkbasketMarkedForDeletionException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException; import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
import pro.taskana.workbasket.api.models.WorkbasketAccessItem; import pro.taskana.workbasket.api.models.WorkbasketAccessItem;
@ -82,10 +84,11 @@ public class WorkbasketServiceImpl implements WorkbasketService {
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
result = workbasketMapper.findById(workbasketId); result = workbasketMapper.findById(workbasketId);
if (result == null) { if (result == null) {
throw new WorkbasketNotFoundException( throw new WorkbasketNotFoundException(workbasketId);
workbasketId, "Workbasket with id " + workbasketId + " was not found.");
} }
if (!taskanaEngine if (!taskanaEngine
.getEngine() .getEngine()
.isUserInRole(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN, TaskanaRole.TASK_ADMIN)) { .isUserInRole(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN, TaskanaRole.TASK_ADMIN)) {
@ -100,30 +103,25 @@ public class WorkbasketServiceImpl implements WorkbasketService {
@Override @Override
public Workbasket getWorkbasket(String workbasketKey, String domain) public Workbasket getWorkbasket(String workbasketKey, String domain)
throws WorkbasketNotFoundException, NotAuthorizedException { throws WorkbasketNotFoundException, NotAuthorizedException {
Workbasket result; if (!taskanaEngine
try { .getEngine()
taskanaEngine.openConnection(); .isUserInRole(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN, TaskanaRole.TASK_ADMIN)) {
result = workbasketMapper.findByKeyAndDomain(workbasketKey, domain); this.checkAuthorization(workbasketKey, domain, WorkbasketPermission.READ);
if (result == null) {
throw new WorkbasketNotFoundException(
workbasketKey,
domain,
"Workbasket with key " + workbasketKey + " and domain " + domain + " was not found.");
}
if (!taskanaEngine
.getEngine()
.isUserInRole(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN, TaskanaRole.TASK_ADMIN)) {
this.checkAuthorization(workbasketKey, domain, WorkbasketPermission.READ);
}
return result;
} finally {
taskanaEngine.returnConnection();
} }
Workbasket workbasket =
taskanaEngine.openAndReturnConnection(
() -> workbasketMapper.findByKeyAndDomain(workbasketKey, domain));
if (workbasket == null) {
throw new WorkbasketNotFoundException(workbasketKey, domain);
}
return workbasket;
} }
@Override @Override
public Workbasket createWorkbasket(Workbasket newWorkbasket) public Workbasket createWorkbasket(Workbasket newWorkbasket)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketAlreadyExistException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketAlreadyExistException,
DomainNotFoundException { DomainNotFoundException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
@ -136,7 +134,8 @@ public class WorkbasketServiceImpl implements WorkbasketService {
Workbasket existingWorkbasket = Workbasket existingWorkbasket =
workbasketMapper.findByKeyAndDomain(newWorkbasket.getKey(), newWorkbasket.getDomain()); workbasketMapper.findByKeyAndDomain(newWorkbasket.getKey(), newWorkbasket.getDomain());
if (existingWorkbasket != null) { if (existingWorkbasket != null) {
throw new WorkbasketAlreadyExistException(existingWorkbasket); throw new WorkbasketAlreadyExistException(
existingWorkbasket.getKey(), existingWorkbasket.getDomain());
} }
if (workbasket.getId() == null || workbasket.getId().isEmpty()) { if (workbasket.getId() == null || workbasket.getId().isEmpty()) {
@ -169,8 +168,8 @@ public class WorkbasketServiceImpl implements WorkbasketService {
@Override @Override
public Workbasket updateWorkbasket(Workbasket workbasketToUpdate) public Workbasket updateWorkbasket(Workbasket workbasketToUpdate)
throws NotAuthorizedException, WorkbasketNotFoundException, ConcurrencyException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketNotFoundException,
InvalidWorkbasketException { ConcurrencyException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
WorkbasketImpl workbasketImplToUpdate = (WorkbasketImpl) workbasketToUpdate; WorkbasketImpl workbasketImplToUpdate = (WorkbasketImpl) workbasketToUpdate;
@ -179,8 +178,21 @@ public class WorkbasketServiceImpl implements WorkbasketService {
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
Workbasket oldWorkbasket = Workbasket oldWorkbasket;
this.getWorkbasket(workbasketImplToUpdate.getKey(), workbasketImplToUpdate.getDomain());
if (workbasketImplToUpdate.getId() == null || workbasketImplToUpdate.getId().isEmpty()) {
oldWorkbasket =
getWorkbasket(workbasketImplToUpdate.getKey(), workbasketImplToUpdate.getDomain());
} else {
oldWorkbasket = getWorkbasket(workbasketImplToUpdate.getId());
// changing key or domain is not allowed
if (!oldWorkbasket.getKey().equals(workbasketToUpdate.getKey())
|| !oldWorkbasket.getDomain().equals(workbasketToUpdate.getDomain())) {
throw new WorkbasketNotFoundException(
workbasketToUpdate.getKey(), workbasketToUpdate.getDomain());
}
}
checkModifiedHasNotChanged(oldWorkbasket, workbasketImplToUpdate); checkModifiedHasNotChanged(oldWorkbasket, workbasketImplToUpdate);
workbasketImplToUpdate.setModified(Instant.now()); workbasketImplToUpdate.setModified(Instant.now());
@ -248,11 +260,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
} }
WorkbasketImpl wb = workbasketMapper.findById(workbasketAccessItem.getWorkbasketId()); WorkbasketImpl wb = workbasketMapper.findById(workbasketAccessItem.getWorkbasketId());
if (wb == null) { if (wb == null) {
throw new WorkbasketNotFoundException( throw new WorkbasketNotFoundException(workbasketAccessItem.getWorkbasketId());
workbasketAccessItem.getWorkbasketId(),
String.format(
"WorkbasketAccessItem %s refers to a not existing workbasket",
workbasketAccessItem));
} }
try { try {
workbasketAccessMapper.insert(accessItem); workbasketAccessMapper.insert(accessItem);
@ -286,7 +294,8 @@ public class WorkbasketServiceImpl implements WorkbasketService {
"UC_ACCESSID_WBID_INDEX_E" // H2 "UC_ACCESSID_WBID_INDEX_E" // H2
); );
if (accessItemExistsIdentifier.anyMatch(e.getMessage()::contains)) { if (accessItemExistsIdentifier.anyMatch(e.getMessage()::contains)) {
throw new WorkbasketAccessItemAlreadyExistException(accessItem); throw new WorkbasketAccessItemAlreadyExistException(
accessItem.getAccessId(), accessItem.getWorkbasketId());
} }
throw e; throw e;
} }
@ -386,40 +395,26 @@ public class WorkbasketServiceImpl implements WorkbasketService {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
if (workbasketMapper.findById(workbasketId) == null) { if (workbasketMapper.findById(workbasketId) == null) {
throw new WorkbasketNotFoundException( throw new WorkbasketNotFoundException(workbasketId);
workbasketId, "Workbasket with id " + workbasketId + " was not found.");
} }
if (skipAuthorizationCheck(requestedPermissions)) { if (skipAuthorizationCheck(requestedPermissions)) {
return; return;
} }
List<String> accessIds = taskanaEngine.getEngine().getCurrentUserContext().getAccessIds(); Optional<List<WorkbasketPermission>> grantedPermissions =
WorkbasketAccessItem wbAcc = Optional.ofNullable(
workbasketAccessMapper.findByWorkbasketAndAccessId(workbasketId, accessIds); workbasketAccessMapper.findByWorkbasketAndAccessId(
if (wbAcc == null) { workbasketId,
throw new NotAuthorizedException( taskanaEngine.getEngine().getCurrentUserContext().getAccessIds()))
"Not authorized. Permission '" .map(this::getPermissionsFromWorkbasketAccessItem);
+ Arrays.toString(requestedPermissions)
+ "' on workbasket '"
+ workbasketId
+ "' is needed.",
taskanaEngine.getEngine().getCurrentUserContext().getUserid());
}
List<WorkbasketPermission> grantedPermissions = if (!grantedPermissions.isPresent()
this.getPermissionsFromWorkbasketAccessItem(wbAcc); || !grantedPermissions.get().containsAll(Arrays.asList(requestedPermissions))) {
throw new MismatchedWorkbasketPermissionException(
for (WorkbasketPermission perm : requestedPermissions) { taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
if (!grantedPermissions.contains(perm)) { workbasketId,
throw new NotAuthorizedException( requestedPermissions);
"Not authorized. Permission '"
+ perm.name()
+ "' on workbasket '"
+ workbasketId
+ "' is needed.",
taskanaEngine.getEngine().getCurrentUserContext().getUserid());
}
} }
} finally { } finally {
taskanaEngine.returnConnection(); taskanaEngine.returnConnection();
@ -434,44 +429,27 @@ public class WorkbasketServiceImpl implements WorkbasketService {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
if (workbasketMapper.findByKeyAndDomain(workbasketKey, domain) == null) { if (workbasketMapper.findByKeyAndDomain(workbasketKey, domain) == null) {
throw new WorkbasketNotFoundException( throw new WorkbasketNotFoundException(workbasketKey, domain);
workbasketKey,
domain,
"Workbasket with key " + workbasketKey + " and domain " + domain + " was not found");
} }
if (skipAuthorizationCheck(requestedPermissions)) { if (skipAuthorizationCheck(requestedPermissions)) {
return; return;
} }
List<String> accessIds = taskanaEngine.getEngine().getCurrentUserContext().getAccessIds();
WorkbasketAccessItem wbAcc =
workbasketAccessMapper.findByWorkbasketKeyDomainAndAccessId(
workbasketKey, domain, accessIds);
if (wbAcc == null) {
throw new NotAuthorizedException(
"Not authorized. Permission '"
+ Arrays.toString(requestedPermissions)
+ "' on workbasket with key '"
+ workbasketKey
+ "' and domain '"
+ domain
+ "' is needed.",
taskanaEngine.getEngine().getCurrentUserContext().getUserid());
}
List<WorkbasketPermission> grantedPermissions =
this.getPermissionsFromWorkbasketAccessItem(wbAcc);
for (WorkbasketPermission perm : requestedPermissions) { Optional<List<WorkbasketPermission>> grantedPermissions =
if (!grantedPermissions.contains(perm)) { Optional.ofNullable(
throw new NotAuthorizedException( workbasketAccessMapper.findByWorkbasketKeyDomainAndAccessId(
"Not authorized. Permission '" workbasketKey,
+ perm.name() domain,
+ "' on workbasket with key '" taskanaEngine.getEngine().getCurrentUserContext().getAccessIds()))
+ workbasketKey .map(this::getPermissionsFromWorkbasketAccessItem);
+ "' and domain '"
+ domain if (!grantedPermissions.isPresent()
+ "' is needed.", || !grantedPermissions.get().containsAll(Arrays.asList(requestedPermissions))) {
taskanaEngine.getEngine().getCurrentUserContext().getUserid()); throw new MismatchedWorkbasketPermissionException(
} taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
workbasketKey,
domain,
requestedPermissions);
} }
} finally { } finally {
taskanaEngine.returnConnection(); taskanaEngine.returnConnection();
@ -480,7 +458,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
@Override @Override
public List<WorkbasketAccessItem> getWorkbasketAccessItems(String workbasketId) public List<WorkbasketAccessItem> getWorkbasketAccessItems(String workbasketId)
throws NotAuthorizedException { throws NotAuthorizedException, WorkbasketNotFoundException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
List<WorkbasketAccessItem> result = new ArrayList<>(); List<WorkbasketAccessItem> result = new ArrayList<>();
try { try {
@ -498,15 +476,16 @@ public class WorkbasketServiceImpl implements WorkbasketService {
public void setWorkbasketAccessItems( public void setWorkbasketAccessItems(
String workbasketId, List<WorkbasketAccessItem> wbAccessItems) String workbasketId, List<WorkbasketAccessItem> wbAccessItems)
throws NotAuthorizedException, WorkbasketAccessItemAlreadyExistException, throws NotAuthorizedException, WorkbasketAccessItemAlreadyExistException,
InvalidArgumentException { InvalidArgumentException, WorkbasketNotFoundException {
taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN); taskanaEngine.getEngine().checkRoleMembership(TaskanaRole.BUSINESS_ADMIN, TaskanaRole.ADMIN);
Set<String> ids = new HashSet<>();
Set<WorkbasketAccessItemImpl> accessItems = Set<WorkbasketAccessItemImpl> accessItems =
checkAccessItemsPreconditionsAndSetId(workbasketId, ids, wbAccessItems); checkAccessItemsPreconditionsAndSetId(workbasketId, wbAccessItems);
try { try {
taskanaEngine.openConnection(); taskanaEngine.openConnection();
// this is necessary to verify that the requested workbasket exists.
getWorkbasket(workbasketId);
List<WorkbasketAccessItemImpl> originalAccessItems = new ArrayList<>(); List<WorkbasketAccessItemImpl> originalAccessItems = new ArrayList<>();
@ -813,11 +792,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
.runAsAdmin(() -> getCountTasksNotCompletedByWorkbasketId(workbasketId)); .runAsAdmin(() -> getCountTasksNotCompletedByWorkbasketId(workbasketId));
if (countTasksNotCompletedInWorkbasket > 0) { if (countTasksNotCompletedInWorkbasket > 0) {
String errorMessage = throw new WorkbasketInUseException(workbasketId);
String.format(
"Workbasket %s contains %s non-completed tasks and can´t be marked for deletion.",
workbasketId, countTasksNotCompletedInWorkbasket);
throw new WorkbasketInUseException(errorMessage);
} }
long countTasksInWorkbasket = long countTasksInWorkbasket =
@ -872,25 +847,10 @@ public class WorkbasketServiceImpl implements WorkbasketService {
if (!deleteWorkbasket(workbasketIdForDeleting)) { if (!deleteWorkbasket(workbasketIdForDeleting)) {
bulkLog.addError( bulkLog.addError(
workbasketIdForDeleting, workbasketIdForDeleting,
new WorkbasketInUseException( new WorkbasketMarkedForDeletionException(workbasketIdForDeleting));
"Workbasket with id "
+ workbasketIdForDeleting
+ " contains completed tasks not deleted and will not be deleted."));
} }
} catch (WorkbasketInUseException ex) {
bulkLog.addError(
workbasketIdForDeleting,
new WorkbasketInUseException(
"Workbasket with id "
+ workbasketIdForDeleting
+ " is in use and will not be deleted."));
} catch (TaskanaException ex) { } catch (TaskanaException ex) {
bulkLog.addError( bulkLog.addError(workbasketIdForDeleting, ex);
workbasketIdForDeleting,
new TaskanaException(
"Workbasket with id "
+ workbasketIdForDeleting
+ " Throw an exception and couldn't be deleted."));
} }
} }
return bulkLog; return bulkLog;
@ -990,22 +950,18 @@ public class WorkbasketServiceImpl implements WorkbasketService {
Workbasket oldWorkbasket, WorkbasketImpl workbasketImplToUpdate) throws ConcurrencyException { Workbasket oldWorkbasket, WorkbasketImpl workbasketImplToUpdate) throws ConcurrencyException {
if (!oldWorkbasket.getModified().equals(workbasketImplToUpdate.getModified())) { if (!oldWorkbasket.getModified().equals(workbasketImplToUpdate.getModified())) {
throw new ConcurrencyException();
throw new ConcurrencyException(
"The current Workbasket has been modified while editing. "
+ "The values can not be updated. Workbasket "
+ workbasketImplToUpdate);
} }
} }
private Set<WorkbasketAccessItemImpl> checkAccessItemsPreconditionsAndSetId( private Set<WorkbasketAccessItemImpl> checkAccessItemsPreconditionsAndSetId(
String workbasketId, Set<String> ids, List<WorkbasketAccessItem> wbAccessItems) String workbasketId, List<WorkbasketAccessItem> wbAccessItems)
throws InvalidArgumentException, WorkbasketAccessItemAlreadyExistException { throws InvalidArgumentException, WorkbasketAccessItemAlreadyExistException {
Set<String> ids = new HashSet<>();
Set<WorkbasketAccessItemImpl> accessItems = new HashSet<>(); Set<WorkbasketAccessItemImpl> accessItems = new HashSet<>();
for (WorkbasketAccessItem workbasketAccessItem : wbAccessItems) { for (WorkbasketAccessItem workbasketAccessItem : wbAccessItems) {
if (workbasketAccessItem != null) { if (workbasketAccessItem != null) {
WorkbasketAccessItemImpl wbAccessItemImpl = (WorkbasketAccessItemImpl) workbasketAccessItem; WorkbasketAccessItemImpl wbAccessItemImpl = (WorkbasketAccessItemImpl) workbasketAccessItem;
@ -1027,7 +983,8 @@ public class WorkbasketServiceImpl implements WorkbasketService {
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_WORKBASKET_AUTHORIZATION)); IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_WORKBASKET_AUTHORIZATION));
} }
if (ids.contains(wbAccessItemImpl.getAccessId())) { if (ids.contains(wbAccessItemImpl.getAccessId())) {
throw new WorkbasketAccessItemAlreadyExistException(wbAccessItemImpl); throw new WorkbasketAccessItemAlreadyExistException(
wbAccessItemImpl.getAccessId(), wbAccessItemImpl.getWorkbasketId());
} }
ids.add(wbAccessItemImpl.getAccessId()); ids.add(wbAccessItemImpl.getAccessId());
accessItems.add(wbAccessItemImpl); accessItems.add(wbAccessItemImpl);
@ -1084,44 +1041,42 @@ public class WorkbasketServiceImpl implements WorkbasketService {
} }
private void validateWorkbasket(Workbasket workbasket) private void validateWorkbasket(Workbasket workbasket)
throws InvalidWorkbasketException, DomainNotFoundException { throws DomainNotFoundException, InvalidArgumentException {
// check that required properties (database not null) are set // check that required properties (database not null) are set
validateNameAndType(workbasket); validateNameAndType(workbasket);
if (workbasket.getId() == null || workbasket.getId().length() == 0) { if (workbasket.getId() == null || workbasket.getId().length() == 0) {
throw new InvalidWorkbasketException("Id must not be null for " + workbasket); throw new InvalidArgumentException("Id must not be null for " + workbasket);
} }
if (workbasket.getKey() == null || workbasket.getKey().length() == 0) { if (workbasket.getKey() == null || workbasket.getKey().length() == 0) {
throw new InvalidWorkbasketException("Key must not be null for " + workbasket); throw new InvalidArgumentException("Key must not be null for " + workbasket);
} }
if (workbasket.getDomain() == null) { if (workbasket.getDomain() == null) {
throw new InvalidWorkbasketException("Domain must not be null for " + workbasket); throw new InvalidArgumentException("Domain must not be null for " + workbasket);
} }
if (!taskanaEngine.domainExists(workbasket.getDomain())) { if (!taskanaEngine.domainExists(workbasket.getDomain())) {
throw new DomainNotFoundException( throw new DomainNotFoundException(workbasket.getDomain());
workbasket.getDomain(),
"Domain " + workbasket.getDomain() + " does not exist in the configuration.");
} }
} }
private void validateId(String workbasketId) throws InvalidArgumentException { private void validateId(String workbasketId) throws InvalidArgumentException {
if (workbasketId == null) { if (workbasketId == null) {
throw new InvalidArgumentException("The WorkbasketId can´t be NULL"); throw new InvalidArgumentException("The WorkbasketId can't be NULL");
} }
if (workbasketId.isEmpty()) { if (workbasketId.isEmpty()) {
throw new InvalidArgumentException("The WorkbasketId can´t be EMPTY for deleteWorkbasket()"); throw new InvalidArgumentException("The WorkbasketId can't be EMPTY for deleteWorkbasket()");
} }
} }
private void validateNameAndType(Workbasket workbasket) throws InvalidWorkbasketException { private void validateNameAndType(Workbasket workbasket) throws InvalidArgumentException {
if (workbasket.getName() == null) { if (workbasket.getName() == null) {
throw new InvalidWorkbasketException("Name must not be NULL for " + workbasket); throw new InvalidArgumentException("Name must not be NULL for " + workbasket);
} }
if (workbasket.getName().length() == 0) { if (workbasket.getName().length() == 0) {
throw new InvalidWorkbasketException("Name must not be EMPTY for " + workbasket); throw new InvalidArgumentException("Name must not be EMPTY for " + workbasket);
} }
if (workbasket.getType() == null) { if (workbasket.getType() == null) {
throw new InvalidWorkbasketException("Type must not be NULL for " + workbasket); throw new InvalidArgumentException("Type must not be NULL for " + workbasket);
} }
} }

View File

@ -11,6 +11,7 @@ import pro.taskana.common.api.ScheduledJob.Type;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.JobServiceImpl; import pro.taskana.common.internal.JobServiceImpl;
import pro.taskana.common.internal.jobs.AbstractTaskanaJob; import pro.taskana.common.internal.jobs.AbstractTaskanaJob;
@ -49,7 +50,7 @@ public class WorkbasketCleanupJob extends AbstractTaskanaJob {
LOGGER.info( LOGGER.info(
"Job ended successfully. {} workbaskets deleted.", totalNumberOfWorkbasketDeleted); "Job ended successfully. {} workbaskets deleted.", totalNumberOfWorkbasketDeleted);
} catch (Exception e) { } catch (Exception e) {
throw new TaskanaException("Error while processing WorkbasketCleanupJob.", e); throw new SystemException("Error while processing WorkbasketCleanupJob.", e);
} finally { } finally {
scheduleNextCleanupJob(); scheduleNextCleanupJob();
} }

View File

@ -1,6 +1,7 @@
package acceptance; package acceptance;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS; import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS; import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;
@ -29,12 +30,17 @@ import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.function.ThrowingConsumer;
import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.common.internal.logging.LoggingAspect; import pro.taskana.common.internal.logging.LoggingAspect;
import pro.taskana.common.internal.util.MapCreator;
/** /**
* Test architecture of classes in taskana. For more info and examples see * Test architecture of classes in taskana. For more info and examples see
@ -80,7 +86,42 @@ class ArchitectureTest {
.should() .should()
.onlyDependOnClassesThat( .onlyDependOnClassesThat(
Predicates.resideOutsideOfPackage("..pro.taskana..internal..") Predicates.resideOutsideOfPackage("..pro.taskana..internal..")
.or(Predicates.assignableTo(LoggingAspect.class))); .or(
Predicates.assignableTo(LoggingAspect.class)
.or(Predicates.assignableTo(MapCreator.class))));
myRule.check(importedClasses);
}
@Test
void utilityClassesShouldNotBeInitializable() {
ArchRule myRule =
classes()
.that()
.resideInAPackage("..util..")
.and()
.areNotNestedClasses()
.should()
.haveOnlyPrivateConstructors();
myRule.check(importedClasses);
}
@Test
@Disabled("until we have renamed all tests")
void testMethodNamesShouldMatchAccordingToOurGuidelines() {
ArchRule myRule =
methods()
.that()
.areAnnotatedWith(Test.class)
.or()
.areAnnotatedWith(TestFactory.class)
.and()
.areNotDeclaredIn(ArchitectureTest.class)
.should()
.bePackagePrivate()
.andShould()
.haveNameMatching("^should_[A-Z][^_]+_(For|When)_[A-Z][^_]+$");
myRule.check(importedClasses); myRule.check(importedClasses);
} }
@ -99,7 +140,19 @@ class ArchitectureTest {
@Test @Test
void onlyExceptionsShouldResideInExceptionPackage() { void onlyExceptionsShouldResideInExceptionPackage() {
ArchRule myRule = ArchRule myRule =
classes().that().resideInAPackage("..exceptions").should().beAssignableTo(Throwable.class); classes()
.that()
.resideInAPackage("..exceptions")
.and()
.doNotBelongToAnyOf(ErrorCode.class)
.should()
.beAssignableTo(
Predicates.assignableTo(TaskanaException.class)
.or(Predicates.assignableTo(TaskanaRuntimeException.class)))
.andShould()
.bePublic()
.andShould()
.haveSimpleNameEndingWith("Exception");
myRule.check(importedClasses); myRule.check(importedClasses);
} }

View File

@ -12,7 +12,6 @@ import pro.taskana.common.internal.persistence.MapTypeHandler;
import pro.taskana.task.internal.models.TaskImpl; import pro.taskana.task.internal.models.TaskImpl;
/** This class contains specific mybatis mappings for task tests. */ /** This class contains specific mybatis mappings for task tests. */
@SuppressWarnings({"checkstyle:LineLength"}) @SuppressWarnings({"checkstyle:LineLength"})
public interface TaskTestMapper { public interface TaskTestMapper {

View File

@ -11,6 +11,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import pro.taskana.classification.api.ClassificationService; import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException; import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException;
import pro.taskana.classification.api.exceptions.MalformedServiceLevelException;
import pro.taskana.classification.api.models.Classification; import pro.taskana.classification.api.models.Classification;
import pro.taskana.classification.internal.models.ClassificationImpl; import pro.taskana.classification.internal.models.ClassificationImpl;
import pro.taskana.common.api.exceptions.DomainNotFoundException; import pro.taskana.common.api.exceptions.DomainNotFoundException;
@ -146,7 +147,7 @@ class CreateClassificationAccTest extends AbstractAccTest {
classification.setServiceLevel("P-1D"); classification.setServiceLevel("P-1D");
assertThatThrownBy(() -> CLASSIFICATION_SERVICE.createClassification(classification)) assertThatThrownBy(() -> CLASSIFICATION_SERVICE.createClassification(classification))
.isInstanceOf(InvalidArgumentException.class); .isInstanceOf(MalformedServiceLevelException.class);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@ -166,7 +167,7 @@ class CreateClassificationAccTest extends AbstractAccTest {
CLASSIFICATION_SERVICE.newClassification("Key2", "DOMAIN_B", "TASK"); CLASSIFICATION_SERVICE.newClassification("Key2", "DOMAIN_B", "TASK");
classification2.setServiceLevel("abc"); classification2.setServiceLevel("abc");
assertThatThrownBy(() -> CLASSIFICATION_SERVICE.createClassification(classification2)) assertThatThrownBy(() -> CLASSIFICATION_SERVICE.createClassification(classification2))
.isInstanceOf(InvalidArgumentException.class); .isInstanceOf(MalformedServiceLevelException.class);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")

View File

@ -141,7 +141,7 @@ class UpdateClassificationAccTest extends AbstractAccTest {
Thread.sleep(20); // to avoid identity of modified timestamps between classification and base Thread.sleep(20); // to avoid identity of modified timestamps between classification and base
classificationService.updateClassification(base); classificationService.updateClassification(base);
classification.setName("NOW IT´S MY TURN"); classification.setName("NOW IT'S MY TURN");
classification.setDescription("IT SHOULD BE TO LATE..."); classification.setDescription("IT SHOULD BE TO LATE...");
ThrowingCallable call = () -> classificationService.updateClassification(classification); ThrowingCallable call = () -> classificationService.updateClassification(classification);
assertThatThrownBy(call).isInstanceOf(ConcurrencyException.class); assertThatThrownBy(call).isInstanceOf(ConcurrencyException.class);

View File

@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import acceptance.AbstractAccTest; import acceptance.AbstractAccTest;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -20,6 +21,7 @@ import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.task.api.CallbackState; import pro.taskana.task.api.CallbackState;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.InvalidCallbackStateException;
import pro.taskana.task.api.exceptions.InvalidStateException; import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary; import pro.taskana.task.api.models.TaskSummary;
@ -64,37 +66,42 @@ class CallbackStateAccTest extends AbstractAccTest {
.isEqualTo(CallbackState.CALLBACK_PROCESSING_REQUIRED); .isEqualTo(CallbackState.CALLBACK_PROCESSING_REQUIRED);
assertThat(createdTask.getState()).isEqualTo(TaskState.READY); assertThat(createdTask.getState()).isEqualTo(TaskState.READY);
String endOfMessage = " cannot be deleted because its callback is not yet processed";
ThrowingCallable call = ThrowingCallable call = () -> taskService.forceDeleteTask(createdTask.getId());
() -> { CallbackState[] expectedCallbackStates = {
taskService.forceDeleteTask(createdTask.getId()); CallbackState.NONE, CallbackState.CLAIMED, CallbackState.CALLBACK_PROCESSING_COMPLETED
}; };
assertThatThrownBy(call) assertThatThrownBy(call)
.isInstanceOf(InvalidStateException.class) .isInstanceOf(InvalidStateException.class)
.hasMessageEndingWith(endOfMessage); .hasMessage(
"Expected callback state of Task with id '%s' to be: '%s', but found '%s'",
createdTask.getId(),
Arrays.toString(expectedCallbackStates),
createdTask.getCallbackState());
final TaskImpl createdTask2 = (TaskImpl) taskService.claim(createdTask.getId()); final TaskImpl createdTask2 = (TaskImpl) taskService.claim(createdTask.getId());
assertThat(createdTask2.getState()).isEqualTo(TaskState.CLAIMED); assertThat(createdTask2.getState()).isEqualTo(TaskState.CLAIMED);
call = call = () -> taskService.forceDeleteTask(createdTask2.getId());
() -> {
taskService.forceDeleteTask(createdTask2.getId());
};
assertThatThrownBy(call) assertThatThrownBy(call)
.isInstanceOf(InvalidStateException.class) .isInstanceOf(InvalidStateException.class)
.hasMessageEndingWith(endOfMessage); .hasMessage(
"Expected callback state of Task with id '%s' to be: '%s', but found '%s'",
createdTask2.getId(),
Arrays.toString(expectedCallbackStates),
createdTask2.getCallbackState());
final TaskImpl createdTask3 = (TaskImpl) taskService.completeTask(createdTask.getId()); final TaskImpl createdTask3 = (TaskImpl) taskService.completeTask(createdTask.getId());
call = call = () -> taskService.forceDeleteTask(createdTask3.getId());
() -> {
taskService.forceDeleteTask(createdTask3.getId());
};
assertThatThrownBy(call) assertThatThrownBy(call)
.isInstanceOf(InvalidStateException.class) .isInstanceOf(InvalidStateException.class)
.hasMessageEndingWith(endOfMessage); .hasMessage(
"Expected callback state of Task with id '%s' to be: '%s', but found '%s'",
createdTask3.getId(),
Arrays.toString(expectedCallbackStates),
createdTask3.getCallbackState());
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@ -143,13 +150,9 @@ class CallbackStateAccTest extends AbstractAccTest {
BulkOperationResults<String, TaskanaException> bulkResult1 = taskService.deleteTasks(taskIds); BulkOperationResults<String, TaskanaException> bulkResult1 = taskService.deleteTasks(taskIds);
assertThat(bulkResult1.containsErrors()).isTrue(); assertThat(bulkResult1.containsErrors()).isTrue();
List<String> failedTaskIds = bulkResult1.getFailedIds();
assertThat(failedTaskIds).hasSize(3); assertThat(bulkResult1.getErrorMap().values())
for (String taskId : failedTaskIds) { .hasOnlyElementsOfType(InvalidCallbackStateException.class);
TaskanaException excpt = bulkResult1.getErrorForId(taskId);
assertThat(excpt.getClass().getName()).isEqualTo(InvalidStateException.class.getName());
}
List<String> externalIds = List<String> externalIds =
List.of( List.of(
createdTask1.getExternalId(), createdTask1.getExternalId(),

View File

@ -17,6 +17,7 @@ import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.util.EnumUtil;
import pro.taskana.common.test.security.JaasExtension; import pro.taskana.common.test.security.JaasExtension;
import pro.taskana.common.test.security.WithAccessId; import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
@ -362,7 +363,9 @@ class CompleteTaskAccTest extends AbstractAccTest {
assertThat(results.getFailedIds()).containsExactlyInAnyOrder(id); assertThat(results.getFailedIds()).containsExactlyInAnyOrder(id);
assertThat(results.getErrorMap().values()).hasOnlyElementsOfType(InvalidStateException.class); assertThat(results.getErrorMap().values()).hasOnlyElementsOfType(InvalidStateException.class);
assertThat(results.getErrorForId(id)) assertThat(results.getErrorForId(id))
.hasMessage("Task with Id %s has to be claimed before.", id); .hasMessage(
"Task with id '%s' is in state: '%s', but must be in one of these states: '[%s]'",
id, TaskState.READY, TaskState.CLAIMED);
} }
@WithAccessId(user = "user-1-2") @WithAccessId(user = "user-1-2")
@ -371,6 +374,8 @@ class CompleteTaskAccTest extends AbstractAccTest {
String id1 = "TKI:300000000000000000000000000000000000"; // task is canceled String id1 = "TKI:300000000000000000000000000000000000"; // task is canceled
String id2 = "TKI:300000000000000000000000000000000010"; // task is terminated String id2 = "TKI:300000000000000000000000000000000010"; // task is terminated
List<String> taskIdList = List.of(id1, id2); List<String> taskIdList = List.of(id1, id2);
TaskState[] requiredStates =
EnumUtil.allValuesExceptFor(TaskState.TERMINATED, TaskState.CANCELLED);
BulkOperationResults<String, TaskanaException> results = TASK_SERVICE.completeTasks(taskIdList); BulkOperationResults<String, TaskanaException> results = TASK_SERVICE.completeTasks(taskIdList);
@ -378,9 +383,13 @@ class CompleteTaskAccTest extends AbstractAccTest {
assertThat(results.getFailedIds()).containsExactlyInAnyOrder(id1, id2); assertThat(results.getFailedIds()).containsExactlyInAnyOrder(id1, id2);
assertThat(results.getErrorMap().values()).hasOnlyElementsOfType(InvalidStateException.class); assertThat(results.getErrorMap().values()).hasOnlyElementsOfType(InvalidStateException.class);
assertThat(results.getErrorForId(id1)) assertThat(results.getErrorForId(id1))
.hasMessage("Cannot complete task %s because it is in state CANCELLED.", id1); .hasMessage(
"Task with id '%s' is in state: '%s', but must be in one of these states: '%s'",
id1, TaskState.CANCELLED, Arrays.toString(requiredStates));
assertThat(results.getErrorForId(id2)) assertThat(results.getErrorForId(id2))
.hasMessage("Cannot complete task %s because it is in state TERMINATED.", id2); .hasMessage(
"Task with id '%s' is in state: '%s', but must be in one of these states: '%s'",
id2, TaskState.TERMINATED, Arrays.toString(requiredStates));
} }
@WithAccessId(user = "user-1-2") @WithAccessId(user = "user-1-2")
@ -483,6 +492,8 @@ class CompleteTaskAccTest extends AbstractAccTest {
String id1 = "TKI:300000000000000000000000000000000000"; // task is canceled String id1 = "TKI:300000000000000000000000000000000000"; // task is canceled
String id2 = "TKI:300000000000000000000000000000000010"; // task is terminated String id2 = "TKI:300000000000000000000000000000000010"; // task is terminated
List<String> taskIdList = List.of(id1, id2); List<String> taskIdList = List.of(id1, id2);
TaskState[] requiredStates =
EnumUtil.allValuesExceptFor(TaskState.TERMINATED, TaskState.CANCELLED);
BulkOperationResults<String, TaskanaException> results = BulkOperationResults<String, TaskanaException> results =
TASK_SERVICE.forceCompleteTasks(taskIdList); TASK_SERVICE.forceCompleteTasks(taskIdList);
@ -491,9 +502,13 @@ class CompleteTaskAccTest extends AbstractAccTest {
assertThat(results.getFailedIds()).containsExactlyInAnyOrder(id1, id2); assertThat(results.getFailedIds()).containsExactlyInAnyOrder(id1, id2);
assertThat(results.getErrorMap().values()).hasOnlyElementsOfType(InvalidStateException.class); assertThat(results.getErrorMap().values()).hasOnlyElementsOfType(InvalidStateException.class);
assertThat(results.getErrorForId(id1)) assertThat(results.getErrorForId(id1))
.hasMessage("Cannot complete task %s because it is in state CANCELLED.", id1); .hasMessage(
"Task with id '%s' is in state: '%s', but must be in one of these states: '%s'",
id1, TaskState.CANCELLED, Arrays.toString(requiredStates));
assertThat(results.getErrorForId(id2)) assertThat(results.getErrorForId(id2))
.hasMessage("Cannot complete task %s because it is in state TERMINATED.", id2); .hasMessage(
"Task with id '%s' is in state: '%s', but must be in one of these states: '%s'",
id2, TaskState.TERMINATED, Arrays.toString(requiredStates));
} }
@WithAccessId(user = "user-1-2") @WithAccessId(user = "user-1-2")

View File

@ -778,8 +778,7 @@ class CreateTaskAccTest extends AbstractAccTest {
@WithAccessId(user = "user-1-1") @WithAccessId(user = "user-1-1")
@Test @Test
void should_FetchAttachmentClassification_When_CreatingTaskWithAttachments() void should_FetchAttachmentClassification_When_CreatingTaskWithAttachments() throws Exception {
throws Exception {
Attachment attachment = taskService.newAttachment(); Attachment attachment = taskService.newAttachment();
attachment.setObjectReference( attachment.setObjectReference(
createObjectReference("COMPANY_A", "SYSTEM_A", "INSTANCE_A", "VNR", "1234567")); createObjectReference("COMPANY_A", "SYSTEM_A", "INSTANCE_A", "VNR", "1234567"));

View File

@ -5,7 +5,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import acceptance.AbstractAccTest; import acceptance.AbstractAccTest;
import acceptance.TaskanaEngineProxy; import acceptance.TaskanaEngineProxy;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -19,6 +18,7 @@ import pro.taskana.common.test.security.JaasExtension;
import pro.taskana.common.test.security.WithAccessId; import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.exceptions.InvalidStateException; import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
import pro.taskana.task.internal.AttachmentMapper; import pro.taskana.task.internal.AttachmentMapper;
@ -27,36 +27,25 @@ import pro.taskana.task.internal.AttachmentMapper;
@ExtendWith(JaasExtension.class) @ExtendWith(JaasExtension.class)
class DeleteTaskAccTest extends AbstractAccTest { class DeleteTaskAccTest extends AbstractAccTest {
DeleteTaskAccTest() { private final TaskService taskService = taskanaEngine.getTaskService();
super();
}
@WithAccessId(user = "user-1-2") @WithAccessId(user = "user-1-2")
@Test @Test
void testDeleteSingleTaskNotAuthorized() { void testDeleteSingleTaskNotAuthorized() {
TaskService taskService = taskanaEngine.getTaskService();
ThrowingCallable call = ThrowingCallable call =
() -> { () -> taskService.deleteTask("TKI:000000000000000000000000000000000037");
taskService.deleteTask("TKI:000000000000000000000000000000000037");
};
assertThatThrownBy(call).isInstanceOf(NotAuthorizedException.class); assertThatThrownBy(call).isInstanceOf(NotAuthorizedException.class);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void should_DeleteAttachments_When_MultipleTasksAreDeleted() throws Exception { void should_DeleteAttachments_When_MultipleTasksAreDeleted() throws Exception {
TaskService taskService = taskanaEngine.getTaskService();
TaskanaEngineProxy engineProxy = new TaskanaEngineProxy(taskanaEngine); TaskanaEngineProxy engineProxy = new TaskanaEngineProxy(taskanaEngine);
AttachmentMapper attachmentMapper = AttachmentMapper attachmentMapper =
engineProxy.getEngine().getSqlSession().getMapper(AttachmentMapper.class); engineProxy.getEngine().getSqlSession().getMapper(AttachmentMapper.class);
try { try {
engineProxy.openConnection(); engineProxy.openConnection();
assertThat( assertThat(
attachmentMapper.findAttachmentSummariesByTaskIds( attachmentMapper.findAttachmentSummariesByTaskIds(
List.of( List.of(
@ -72,14 +61,13 @@ class DeleteTaskAccTest extends AbstractAccTest {
"TKI:000000000000000000000000000000000067", "TKI:000000000000000000000000000000000067",
"TKI:000000000000000000000000000000000068")); "TKI:000000000000000000000000000000000068"));
try { try {
engineProxy.openConnection();
assertThat( assertThat(
attachmentMapper.findAttachmentSummariesByTaskIds( attachmentMapper.findAttachmentSummariesByTaskIds(
List.of( List.of(
"TKI:000000000000000000000000000000000067", "TKI:000000000000000000000000000000000067",
"TKI:000000000000000000000000000000000068"))) "TKI:000000000000000000000000000000000068")))
.isEmpty(); .isEmpty();
} finally { } finally {
engineProxy.returnConnection(); engineProxy.returnConnection();
} }
@ -88,17 +76,12 @@ class DeleteTaskAccTest extends AbstractAccTest {
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void should_DeleteAttachments_When_SingleTaskIsDeleted() throws Exception { void should_DeleteAttachments_When_SingleTaskIsDeleted() throws Exception {
TaskService taskService = taskanaEngine.getTaskService();
TaskanaEngineProxy engineProxy = new TaskanaEngineProxy(taskanaEngine); TaskanaEngineProxy engineProxy = new TaskanaEngineProxy(taskanaEngine);
AttachmentMapper attachmentMapper = AttachmentMapper attachmentMapper =
engineProxy.getSqlSession().getMapper(AttachmentMapper.class); engineProxy.getSqlSession().getMapper(AttachmentMapper.class);
try { try {
engineProxy.openConnection(); engineProxy.openConnection();
assertThat( assertThat(
attachmentMapper.findAttachmentsByTaskId("TKI:000000000000000000000000000000000069")) attachmentMapper.findAttachmentsByTaskId("TKI:000000000000000000000000000000000069"))
.hasSize(1); .hasSize(1);
@ -110,7 +93,7 @@ class DeleteTaskAccTest extends AbstractAccTest {
taskService.deleteTask("TKI:000000000000000000000000000000000069"); taskService.deleteTask("TKI:000000000000000000000000000000000069");
try { try {
engineProxy.openConnection();
assertThat( assertThat(
attachmentMapper.findAttachmentsByTaskId("TKI:000000000000000000000000000000000069")) attachmentMapper.findAttachmentsByTaskId("TKI:000000000000000000000000000000000069"))
.isEmpty(); .isEmpty();
@ -124,9 +107,6 @@ class DeleteTaskAccTest extends AbstractAccTest {
@WithAccessId(user = "user-1-1") @WithAccessId(user = "user-1-1")
@TestTemplate @TestTemplate
void should_ThrowException_When_UserIsNotInAdminRoleButTriesToBulkDeleteTasks() { void should_ThrowException_When_UserIsNotInAdminRoleButTriesToBulkDeleteTasks() {
TaskService taskService = taskanaEngine.getTaskService();
List<String> taskIds = List<String> taskIds =
List.of( List.of(
"TKI:000000000000000000000000000000000008", "TKI:000000000000000000000000000000000008",
@ -134,26 +114,18 @@ class DeleteTaskAccTest extends AbstractAccTest {
"TKI:000000000000000000000000000000000008", "TKI:000000000000000000000000000000000008",
"TKI:000000000000000000000000000000000010"); "TKI:000000000000000000000000000000000010");
ThrowingCallable call = ThrowingCallable call = () -> taskService.deleteTasks(taskIds);
() -> {
taskService.deleteTasks(taskIds);
};
assertThatThrownBy(call).isInstanceOf(NotAuthorizedException.class); assertThatThrownBy(call).isInstanceOf(NotAuthorizedException.class);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void testDeleteSingleTask() throws Exception { void testDeleteSingleTask() throws Exception {
TaskService taskService = taskanaEngine.getTaskService();
Task task = taskService.getTask("TKI:000000000000000000000000000000000036"); Task task = taskService.getTask("TKI:000000000000000000000000000000000036");
taskService.deleteTask(task.getId()); taskService.deleteTask(task.getId());
ThrowingCallable call = ThrowingCallable call = () -> taskService.getTask("TKI:000000000000000000000000000000000036");
() -> {
taskService.getTask("TKI:000000000000000000000000000000000036");
};
assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class); assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class);
} }
@ -162,97 +134,71 @@ class DeleteTaskAccTest extends AbstractAccTest {
@WithAccessId(user = "user-1-1") @WithAccessId(user = "user-1-1")
@TestTemplate @TestTemplate
void should_ThrowException_When_UserIsNotInAdminRole() { void should_ThrowException_When_UserIsNotInAdminRole() {
TaskService taskService = taskanaEngine.getTaskService();
ThrowingCallable deleteTaskCall = ThrowingCallable deleteTaskCall =
() -> { () -> taskService.deleteTask("TKI:000000000000000000000000000000000041");
taskService.deleteTask("TKI:000000000000000000000000000000000041");
};
assertThatThrownBy(deleteTaskCall).isInstanceOf(NotAuthorizedException.class); assertThatThrownBy(deleteTaskCall).isInstanceOf(NotAuthorizedException.class);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void testThrowsExceptionIfTaskIsNotCompleted() throws Exception { void testThrowsExceptionIfTaskIsNotCompleted() throws Exception {
TaskService taskService = taskanaEngine.getTaskService();
Task task = taskService.getTask("TKI:000000000000000000000000000000000029"); Task task = taskService.getTask("TKI:000000000000000000000000000000000029");
ThrowingCallable call = ThrowingCallable call = () -> taskService.deleteTask(task.getId());
() -> {
taskService.deleteTask(task.getId());
};
assertThatThrownBy(call).isInstanceOf(InvalidStateException.class); assertThatThrownBy(call).isInstanceOf(InvalidStateException.class);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void testForceDeleteTaskIfNotCompleted() throws Exception { void testForceDeleteTaskIfNotCompleted() throws Exception {
TaskService taskService = taskanaEngine.getTaskService();
Task task = taskService.getTask("TKI:000000000000000000000000000000000027"); Task task = taskService.getTask("TKI:000000000000000000000000000000000027");
ThrowingCallable call = ThrowingCallable call = () -> taskService.deleteTask(task.getId());
() -> {
taskService.deleteTask(task.getId());
};
assertThatThrownBy(call) assertThatThrownBy(call)
.describedAs("Should not be possible to delete claimed task without force flag") .describedAs("Should not be possible to delete claimed task without force flag")
.isInstanceOf(InvalidStateException.class); .isInstanceOf(InvalidStateException.class);
taskService.forceDeleteTask(task.getId()); taskService.forceDeleteTask(task.getId());
call = call = () -> taskService.getTask("TKI:000000000000000000000000000000000027");
() -> {
taskService.getTask("TKI:000000000000000000000000000000000027");
};
assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class); assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void testBulkDeleteTask() throws Exception { void testBulkDeleteTask() throws Exception {
List<String> taskIdList =
TaskService taskService = taskanaEngine.getTaskService(); List.of(
ArrayList<String> taskIdList = new ArrayList<>(); "TKI:000000000000000000000000000000000037", "TKI:000000000000000000000000000000000038");
taskIdList.add("TKI:000000000000000000000000000000000037");
taskIdList.add("TKI:000000000000000000000000000000000038");
BulkOperationResults<String, TaskanaException> results = taskService.deleteTasks(taskIdList); BulkOperationResults<String, TaskanaException> results = taskService.deleteTasks(taskIdList);
assertThat(results.containsErrors()).isFalse(); assertThat(results.containsErrors()).isFalse();
ThrowingCallable call = ThrowingCallable call = () -> taskService.getTask("TKI:000000000000000000000000000000000038");
() -> {
taskService.getTask("TKI:000000000000000000000000000000000038");
};
assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class); assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void testBulkDeleteTasksWithException() throws Exception { void testBulkDeleteTasksWithException() throws Exception {
List<String> taskIdList =
TaskService taskService = taskanaEngine.getTaskService(); List.of(
ArrayList<String> taskIdList = new ArrayList<>(); "TKI:000000000000000000000000000000000039",
taskIdList.add("TKI:000000000000000000000000000000000039"); "TKI:000000000000000000000000000000000040",
taskIdList.add("TKI:000000000000000000000000000000000040"); "TKI:000000000000000000000000000000000028");
taskIdList.add("TKI:000000000000000000000000000000000028");
BulkOperationResults<String, TaskanaException> results = taskService.deleteTasks(taskIdList); BulkOperationResults<String, TaskanaException> results = taskService.deleteTasks(taskIdList);
String expectedFailedId = "TKI:000000000000000000000000000000000028";
assertThat(results.containsErrors()).isTrue(); assertThat(results.containsErrors()).isTrue();
List<String> failedTaskIds = results.getFailedIds();
assertThat(failedTaskIds).hasSize(1); assertThat(results.getErrorMap().keySet())
assertThat(failedTaskIds.get(0)).isEqualTo(expectedFailedId); .containsExactly("TKI:000000000000000000000000000000000028");
assertThat(InvalidStateException.class) assertThat(results.getErrorMap().values())
.isSameAs(results.getErrorMap().get(expectedFailedId).getClass()); .hasOnlyElementsOfType(InvalidTaskStateException.class);
Task notDeletedTask = taskService.getTask("TKI:000000000000000000000000000000000028"); Task notDeletedTask = taskService.getTask("TKI:000000000000000000000000000000000028");
assertThat(notDeletedTask).isNotNull(); assertThat(notDeletedTask).isNotNull();
ThrowingCallable call = ThrowingCallable call = () -> taskService.getTask("TKI:000000000000000000000000000000000040");
() -> {
taskService.getTask("TKI:000000000000000000000000000000000040");
};
assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class); assertThatThrownBy(call).isInstanceOf(TaskNotFoundException.class);
} }
} }

View File

@ -115,8 +115,7 @@ class QueryTasksByObjectReferenceAccTest extends AbstractAccTest {
objectReference.setSystemInstance("00"); objectReference.setSystemInstance("00");
objectReference.setType("VNR"); objectReference.setType("VNR");
objectReference.setValue("67890123"); objectReference.setValue("67890123");
long count = long count = TASK_SERVICE.createTaskQuery().primaryObjectReferenceIn(objectReference).count();
TASK_SERVICE.createTaskQuery().primaryObjectReferenceIn(objectReference).count();
assertThat(count).isEqualTo(1); assertThat(count).isEqualTo(1);
} }

View File

@ -21,9 +21,11 @@ import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.InvalidStateException; import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary; import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
/** Acceptance test for all "set owner" scenarios. */ /** Acceptance test for all "set owner" scenarios. */
@ExtendWith(JaasExtension.class) @ExtendWith(JaasExtension.class)
@ -85,11 +87,12 @@ class SetOwnerAccTest extends AbstractAccTest {
String anyUserName = "TestUser3"; String anyUserName = "TestUser3";
assertThatThrownBy(() -> taskService.getTask(taskReadyId)) assertThatThrownBy(() -> taskService.getTask(taskReadyId))
.isInstanceOf(NotAuthorizedException.class); .isInstanceOf(MismatchedWorkbasketPermissionException.class);
BulkOperationResults<String, TaskanaException> results = BulkOperationResults<String, TaskanaException> results =
taskService.setOwnerOfTasks(anyUserName, List.of(taskReadyId)); taskService.setOwnerOfTasks(anyUserName, List.of(taskReadyId));
assertThat(results.containsErrors()).isTrue(); assertThat(results.containsErrors()).isTrue();
assertThat(results.getErrorForId(taskReadyId)).isInstanceOf(NotAuthorizedException.class); assertThat(results.getErrorForId(taskReadyId))
.isInstanceOf(MismatchedWorkbasketPermissionException.class);
} }
@WithAccessId(user = "user-b-2") @WithAccessId(user = "user-b-2")
@ -156,7 +159,8 @@ class SetOwnerAccTest extends AbstractAccTest {
resetDb(false); resetDb(false);
List<TaskSummary> allTaskSummaries = List<TaskSummary> allTaskSummaries =
new TaskanaEngineProxy(taskanaEngine) new TaskanaEngineProxy(taskanaEngine)
.getEngine().getEngine() .getEngine()
.getEngine()
.runAsAdmin(() -> taskanaEngine.getTaskService().createTaskQuery().list()); .runAsAdmin(() -> taskanaEngine.getTaskService().createTaskQuery().list());
List<String> allTaskIds = List<String> allTaskIds =
allTaskSummaries.stream().map(TaskSummary::getId).collect(Collectors.toList()); allTaskSummaries.stream().map(TaskSummary::getId).collect(Collectors.toList());
@ -165,17 +169,19 @@ class SetOwnerAccTest extends AbstractAccTest {
assertThat(allTaskSummaries).hasSize(88); assertThat(allTaskSummaries).hasSize(88);
assertThat(results.containsErrors()).isTrue(); assertThat(results.containsErrors()).isTrue();
Condition<Object> invalidStateException = Condition<Object> invalidTaskStateException =
new Condition<>(c -> c.getClass() == InvalidStateException.class, "InvalidStateException");
Condition<Object> notAuthorizedException =
new Condition<>( new Condition<>(
c -> c.getClass() == NotAuthorizedException.class, "NotAuthorizedException"); c -> c.getClass() == InvalidTaskStateException.class, "InvalidStateException");
Condition<Object> mismatchedWorkbasketPermissionException =
new Condition<>(
c -> c.getClass() == MismatchedWorkbasketPermissionException.class,
"MismatchedWorkbasketPermissionException");
assertThat(results.getErrorMap()) assertThat(results.getErrorMap())
.hasSize(86) .hasSize(86)
.extractingFromEntries(Entry::getValue) .extractingFromEntries(Entry::getValue)
.hasOnlyElementsOfTypes(InvalidStateException.class, NotAuthorizedException.class) .hasOnlyElementsOfTypes(InvalidTaskStateException.class, NotAuthorizedException.class)
.areExactly(28, invalidStateException) .areExactly(28, invalidTaskStateException)
.areExactly(58, notAuthorizedException); .areExactly(58, mismatchedWorkbasketPermissionException);
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")

View File

@ -37,7 +37,8 @@ class TaskEngineAccTest extends AbstractAccTest {
assertThat(taskanaEngine.isUserInRole(TaskanaRole.ADMIN)).isFalse(); assertThat(taskanaEngine.isUserInRole(TaskanaRole.ADMIN)).isFalse();
new TaskanaEngineProxy(taskanaEngine) new TaskanaEngineProxy(taskanaEngine)
.getEngine().getEngine() .getEngine()
.getEngine()
.runAsAdmin(() -> assertThat(taskanaEngine.isUserInRole(TaskanaRole.ADMIN)).isTrue()); .runAsAdmin(() -> assertThat(taskanaEngine.isUserInRole(TaskanaRole.ADMIN)).isTrue());
assertThat(taskanaEngine.isUserInRole(TaskanaRole.ADMIN)).isFalse(); assertThat(taskanaEngine.isUserInRole(TaskanaRole.ADMIN)).isFalse();

View File

@ -24,9 +24,11 @@ import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.task.api.TaskService; import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.TaskState; import pro.taskana.task.api.TaskState;
import pro.taskana.task.api.exceptions.InvalidStateException; import pro.taskana.task.api.exceptions.InvalidStateException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary; import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException; import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
@ -140,8 +142,8 @@ class TransferTaskAccTest extends AbstractAccTest {
.extracting(Throwable::getMessage) .extracting(Throwable::getMessage)
.asString() .asString()
.startsWith( .startsWith(
"Not authorized. Permission 'TRANSFER' on workbasket " "Not authorized. The current user 'teamlead-1' has no '[TRANSFER]' permission(s) "
+ "'WBI:100000000000000000000000000000000005' is needed."); + "for Workbasket 'WBI:100000000000000000000000000000000005'.");
} }
@WithAccessId(user = "user-1-1", groups = GROUP_1_DN) @WithAccessId(user = "user-1-1", groups = GROUP_1_DN)
@ -156,8 +158,8 @@ class TransferTaskAccTest extends AbstractAccTest {
.extracting(Throwable::getMessage) .extracting(Throwable::getMessage)
.asString() .asString()
.startsWith( .startsWith(
"Not authorized. Permission 'APPEND' on workbasket " "Not authorized. The current user 'user-1-1' has no '[APPEND]' permission(s) "
+ "'WBI:100000000000000000000000000000000008' is needed."); + "for Workbasket 'WBI:100000000000000000000000000000000008'.");
} }
@WithAccessId(user = "teamlead-1") @WithAccessId(user = "teamlead-1")
@ -229,13 +231,13 @@ class TransferTaskAccTest extends AbstractAccTest {
assertThat(results.containsErrors()).isTrue(); assertThat(results.containsErrors()).isTrue();
assertThat(results.getErrorMap().values()).hasSize(6); assertThat(results.getErrorMap().values()).hasSize(6);
assertThat(results.getErrorForId("TKI:000000000000000000000000000000000041").getClass()) assertThat(results.getErrorForId("TKI:000000000000000000000000000000000041").getClass())
.isEqualTo(NotAuthorizedException.class); .isEqualTo(MismatchedWorkbasketPermissionException.class);
assertThat(results.getErrorForId("TKI:200000000000000000000000000000000008").getClass()) assertThat(results.getErrorForId("TKI:200000000000000000000000000000000008").getClass())
.isEqualTo(NotAuthorizedException.class); .isEqualTo(MismatchedWorkbasketPermissionException.class);
assertThat(results.getErrorForId("TKI:000000000000000000000000000000000099").getClass()) assertThat(results.getErrorForId("TKI:000000000000000000000000000000000099").getClass())
.isEqualTo(TaskNotFoundException.class); .isEqualTo(TaskNotFoundException.class);
assertThat(results.getErrorForId("TKI:100000000000000000000000000000000006").getClass()) assertThat(results.getErrorForId("TKI:100000000000000000000000000000000006").getClass())
.isEqualTo(InvalidStateException.class); .isEqualTo(InvalidTaskStateException.class);
assertThat(results.getErrorForId("").getClass()).isEqualTo(InvalidArgumentException.class); assertThat(results.getErrorForId("").getClass()).isEqualTo(InvalidArgumentException.class);
assertThat(results.getErrorForId(null).getClass()).isEqualTo(InvalidArgumentException.class); assertThat(results.getErrorForId(null).getClass()).isEqualTo(InvalidArgumentException.class);

View File

@ -136,7 +136,8 @@ class UpdateTaskAccTest extends AbstractAccTest {
// TODO flaky test ... if speed is too high, // TODO flaky test ... if speed is too high,
assertThatThrownBy(() -> taskService.updateTask(task2)) assertThatThrownBy(() -> taskService.updateTask(task2))
.isInstanceOf(ConcurrencyException.class) .isInstanceOf(ConcurrencyException.class)
.hasMessage("The task has already been updated by another user"); .hasMessage(
"The current entity cannot be updated since it has been modified while editing.");
} }
@WithAccessId(user = "admin") @WithAccessId(user = "admin")

View File

@ -588,8 +588,7 @@ class UpdateTaskAttachmentsAccTest extends AbstractAccTest {
@WithAccessId(user = "user-1-1") @WithAccessId(user = "user-1-1")
@Test @Test
void should_FetchAttachmentClassification_When_UpdatingTaskWithAttachments() void should_FetchAttachmentClassification_When_UpdatingTaskWithAttachments() throws Exception {
throws Exception {
ClassificationSummary classification = ClassificationSummary classification =
classificationService.newClassification("T2000", "DOMAIN_A", "").asSummary(); classificationService.newClassification("T2000", "DOMAIN_A", "").asSummary();
attachment.setClassificationSummary(classification); attachment.setClassificationSummary(classification);

View File

@ -11,13 +11,13 @@ import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import pro.taskana.common.api.exceptions.DomainNotFoundException; import pro.taskana.common.api.exceptions.DomainNotFoundException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.test.security.JaasExtension; import pro.taskana.common.test.security.JaasExtension;
import pro.taskana.common.test.security.WithAccessId; import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.workbasket.api.WorkbasketPermission; import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService; import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.WorkbasketType; import pro.taskana.workbasket.api.WorkbasketType;
import pro.taskana.workbasket.api.exceptions.InvalidWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
@ -111,21 +111,21 @@ class CreateWorkbasketAccTest extends AbstractAccTest {
workbasket.setOrgLevel1("company"); workbasket.setOrgLevel1("company");
// missing key // missing key
assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket)) assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
Workbasket workbasket2 = workbasketService.newWorkbasket("key", "novatec"); Workbasket workbasket2 = workbasketService.newWorkbasket("key", "novatec");
workbasket2.setType(WorkbasketType.GROUP); workbasket2.setType(WorkbasketType.GROUP);
workbasket2.setOrgLevel1("company"); workbasket2.setOrgLevel1("company");
// missing name // missing name
assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket2)) assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket2))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
Workbasket workbasket3 = workbasketService.newWorkbasket("key", "novatec"); Workbasket workbasket3 = workbasketService.newWorkbasket("key", "novatec");
workbasket3.setName("Megabasket"); workbasket3.setName("Megabasket");
workbasket3.setOrgLevel1("company"); workbasket3.setOrgLevel1("company");
// missing type // missing type
assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket3)) assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket3))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
Workbasket workbasket4 = workbasketService.newWorkbasket("key", null); Workbasket workbasket4 = workbasketService.newWorkbasket("key", null);
workbasket4.setName("Megabasket"); workbasket4.setName("Megabasket");
@ -133,7 +133,7 @@ class CreateWorkbasketAccTest extends AbstractAccTest {
workbasket4.setOrgLevel1("company"); workbasket4.setOrgLevel1("company");
// missing domain // missing domain
assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket4)) assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket4))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
Workbasket workbasket5 = workbasketService.newWorkbasket("", "novatec"); Workbasket workbasket5 = workbasketService.newWorkbasket("", "novatec");
workbasket5.setName("Megabasket"); workbasket5.setName("Megabasket");
@ -141,7 +141,7 @@ class CreateWorkbasketAccTest extends AbstractAccTest {
workbasket5.setOrgLevel1("company"); workbasket5.setOrgLevel1("company");
// empty key // empty key
assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket5)) assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket5))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
Workbasket workbasket6 = workbasketService.newWorkbasket("key", "novatec"); Workbasket workbasket6 = workbasketService.newWorkbasket("key", "novatec");
workbasket6.setName(""); workbasket6.setName("");
@ -149,7 +149,7 @@ class CreateWorkbasketAccTest extends AbstractAccTest {
workbasket6.setOrgLevel1("company"); workbasket6.setOrgLevel1("company");
// empty name // empty name
assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket)) assertThatThrownBy(() -> workbasketService.createWorkbasket(workbasket))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")

View File

@ -40,7 +40,8 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@Test @Test
void testDeleteWorkbasket() throws Exception { void should_ThrowWorkbasketNotFoundException_When_TheWorkbasketHasAlreadyBeenDeleted()
throws Exception {
Workbasket wb = workbasketService.getWorkbasket("USER-2-2", "DOMAIN_A"); Workbasket wb = workbasketService.getWorkbasket("USER-2-2", "DOMAIN_A");
ThrowingCallable call = ThrowingCallable call =
@ -75,17 +76,19 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
} }
@Test @Test
void testGetWorkbasketNotAuthorized() { void should_ThrowNotAuthorizedException_When_UnauthorizedTryingToGetWorkbaskets() {
assertThatThrownBy(() -> workbasketService.getWorkbasket("TEAMLEAD-2", "DOMAIN_A")) assertThatThrownBy(() -> workbasketService.getWorkbasket("TEAMLEAD-2", "DOMAIN_A"))
.isInstanceOf(NotAuthorizedException.class); .isInstanceOf(NotAuthorizedException.class);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@Test @Test
void testDeleteWorkbasketAlsoAsDistributionTarget() throws Exception { void should_RemoveWorkbasketsFromDistributionTargets_WhenWorkbasketIsDeleted() throws Exception {
Workbasket wb = workbasketService.getWorkbasket("GPK_KSC_1", "DOMAIN_A"); Workbasket wb = workbasketService.getWorkbasket("GPK_KSC_1", "DOMAIN_A");
int distTargets = int distTargets =
workbasketService.getDistributionTargets("WBI:100000000000000000000000000000000001").size(); workbasketService
.getDistributionTargets("GPK_KSC", "DOMAIN_A")
.size(); // has GPK_KSC_1 as a distribution target (+ 3 other Workbaskets)
ThrowingCallable call = ThrowingCallable call =
() -> { () -> {
@ -97,14 +100,13 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
.describedAs("There should be no result for a deleted Workbasket.") .describedAs("There should be no result for a deleted Workbasket.")
.isInstanceOf(WorkbasketNotFoundException.class); .isInstanceOf(WorkbasketNotFoundException.class);
int newDistTargets = int newDistTargets = workbasketService.getDistributionTargets("GPK_KSC", "DOMAIN_A").size();
workbasketService.getDistributionTargets("WBI:100000000000000000000000000000000001").size();
assertThat(newDistTargets).isEqualTo(3).isLessThan(distTargets); assertThat(newDistTargets).isEqualTo(3).isLessThan(distTargets);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@Test @Test
void testDeleteWorkbasketWithNullOrEmptyParam() { void should_ThrowInvalidArgumentException_When_TryingToDeleteNullOrEmptyWorkbasket() {
// Test Null-Value // Test Null-Value
assertThatThrownBy(() -> workbasketService.deleteWorkbasket(null)) assertThatThrownBy(() -> workbasketService.deleteWorkbasket(null))
.describedAs( .describedAs(
@ -122,14 +124,15 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@Test @Test
void testDeleteWorkbasketButNotExisting() { void should_ThrowWorkbasketNotFoundException_When_TryingToDeleteNonExistingWorkbasket() {
assertThatThrownBy(() -> workbasketService.deleteWorkbasket("SOME NOT EXISTING ID")) assertThatThrownBy(() -> workbasketService.deleteWorkbasket("SOME NOT EXISTING ID"))
.isInstanceOf(WorkbasketNotFoundException.class); .isInstanceOf(WorkbasketNotFoundException.class);
} }
@WithAccessId(user = "user-1-2", groups = "businessadmin") @WithAccessId(user = "user-1-2", groups = "businessadmin")
@Test @Test
void testDeleteWorkbasketWhichIsUsed() throws Exception { void should_ThrowWorkbasketInUseException_When_TryingToDeleteWorkbasketWhichIsInUse()
throws Exception {
Workbasket wb = Workbasket wb =
workbasketService.getWorkbasket("user-1-2", "DOMAIN_A"); // all rights, DOMAIN_A with Tasks workbasketService.getWorkbasket("user-1-2", "DOMAIN_A"); // all rights, DOMAIN_A with Tasks
assertThatThrownBy(() -> workbasketService.deleteWorkbasket(wb.getId())) assertThatThrownBy(() -> workbasketService.deleteWorkbasket(wb.getId()))
@ -138,7 +141,7 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@Test @Test
void testCascadingDeleteOfAccessItems() throws Exception { void should_DeleteWorkbasketAccessItems_When_WorkbasketIsDeleted() throws Exception {
Workbasket wb = workbasketService.getWorkbasket("WBI:100000000000000000000000000000000008"); Workbasket wb = workbasketService.getWorkbasket("WBI:100000000000000000000000000000000008");
String wbId = wb.getId(); String wbId = wb.getId();
// create 2 access Items // create 2 access Items
@ -170,7 +173,7 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
@WithAccessId(user = "admin") @WithAccessId(user = "admin")
@Test @Test
void testMarkWorkbasketForDeletion() throws Exception { void should_MarkWorkbasketForDeletion_When_TryingToDeleteWorkbasketWithTasks() throws Exception {
final Workbasket wb = final Workbasket wb =
workbasketService.getWorkbasket("WBI:100000000000000000000000000000000006"); workbasketService.getWorkbasket("WBI:100000000000000000000000000000000006");
@ -183,8 +186,8 @@ class DeleteWorkbasketAccTest extends AbstractAccTest {
task = (TaskImpl) taskService.getTask("TKI:000000000000000000000000000000000066"); task = (TaskImpl) taskService.getTask("TKI:000000000000000000000000000000000066");
taskService.forceCompleteTask(task.getId()); taskService.forceCompleteTask(task.getId());
boolean markedForDeletion = workbasketService.deleteWorkbasket(wb.getId()); boolean canBeDeletedNow = workbasketService.deleteWorkbasket(wb.getId());
assertThat(markedForDeletion).isFalse(); assertThat(canBeDeletedNow).isFalse();
Workbasket wb2 = workbasketService.getWorkbasket(wb.getId()); Workbasket wb2 = workbasketService.getWorkbasket(wb.getId());
assertThat(wb2.isMarkedForDeletion()).isTrue(); assertThat(wb2.isMarkedForDeletion()).isTrue();

View File

@ -19,7 +19,7 @@ class GetWorkbasketAuthorizationsAccTest extends AbstractAccTest {
@WithAccessId(user = "user-1-1") @WithAccessId(user = "user-1-1")
@WithAccessId(user = "taskadmin") @WithAccessId(user = "taskadmin")
@TestTemplate @TestTemplate
void should_ThrowException_When_UserRoleIsNotAdminOrBusinessAdmin() { void should_ThrowNotAuthorizedException_When_UserRoleIsNotAdminOrBusinessAdmin() {
final WorkbasketService workbasketService = taskanaEngine.getWorkbasketService(); final WorkbasketService workbasketService = taskanaEngine.getWorkbasketService();

View File

@ -12,13 +12,13 @@ import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import pro.taskana.common.api.exceptions.ConcurrencyException; import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.test.security.JaasExtension; import pro.taskana.common.test.security.JaasExtension;
import pro.taskana.common.test.security.WithAccessId; import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.workbasket.api.WorkbasketCustomField; import pro.taskana.workbasket.api.WorkbasketCustomField;
import pro.taskana.workbasket.api.WorkbasketService; import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.WorkbasketType; import pro.taskana.workbasket.api.WorkbasketType;
import pro.taskana.workbasket.api.exceptions.InvalidWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException; import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
import pro.taskana.workbasket.internal.models.WorkbasketImpl; import pro.taskana.workbasket.internal.models.WorkbasketImpl;
@ -67,11 +67,11 @@ class UpdateWorkbasketAccTest extends AbstractAccTest {
(WorkbasketImpl) workbasketService.getWorkbasket("GPK_KSC", "DOMAIN_A"); (WorkbasketImpl) workbasketService.getWorkbasket("GPK_KSC", "DOMAIN_A");
workbasket.setName(null); workbasket.setName(null);
assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket)) assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
workbasket.setName(""); workbasket.setName("");
assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket)) assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@ -84,7 +84,7 @@ class UpdateWorkbasketAccTest extends AbstractAccTest {
workbasket.setType(null); workbasket.setType(null);
assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket)) assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket))
.isInstanceOf(InvalidWorkbasketException.class); .isInstanceOf(InvalidArgumentException.class);
} }
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@ -118,6 +118,20 @@ class UpdateWorkbasketAccTest extends AbstractAccTest {
.isThrownBy(() -> workbasketService.updateWorkbasket(workbasket)); .isThrownBy(() -> workbasketService.updateWorkbasket(workbasket));
} }
@WithAccessId(user = "businessadmin")
@Test
void should_ThrowException_When_TryingToUpdateUnknownWorkbasket() {
WorkbasketService workbasketService = taskanaEngine.getWorkbasketService();
Workbasket workbasket = workbasketService.newWorkbasket("InvalidKey", "InvalidDomain");
workbasket.setName("bla bla");
workbasket.setType(WorkbasketType.PERSONAL);
assertThatThrownBy(() -> workbasketService.updateWorkbasket(workbasket))
.isInstanceOf(WorkbasketNotFoundException.class);
}
@WithAccessId(user = "user-1-1") @WithAccessId(user = "user-1-1")
@WithAccessId(user = "taskadmin") @WithAccessId(user = "taskadmin")
@TestTemplate @TestTemplate

View File

@ -25,6 +25,7 @@ import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.workbasket.api.WorkbasketPermission; import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService; import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.NotAuthorizedToQueryWorkbasketException; import pro.taskana.workbasket.api.exceptions.NotAuthorizedToQueryWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.WorkbasketAccessItem; import pro.taskana.workbasket.api.models.WorkbasketAccessItem;
import pro.taskana.workbasket.internal.models.WorkbasketAccessItemImpl; import pro.taskana.workbasket.internal.models.WorkbasketAccessItemImpl;
@ -43,12 +44,27 @@ class UpdateWorkbasketAuthorizationsAccTest extends AbstractAccTest {
workbasketService.newWorkbasketAccessItem( workbasketService.newWorkbasketAccessItem(
"WBI:100000000000000000000000000000000008", "newAccessIdForUpdate"); "WBI:100000000000000000000000000000000008", "newAccessIdForUpdate");
workbasketAccessItem.setPermission(WorkbasketPermission.CUSTOM_1, true);
assertThatThrownBy(() -> workbasketService.updateWorkbasketAccessItem(workbasketAccessItem)) assertThatThrownBy(() -> workbasketService.updateWorkbasketAccessItem(workbasketAccessItem))
.isInstanceOf(NotAuthorizedException.class); .isInstanceOf(NotAuthorizedException.class);
} }
@Test
@WithAccessId(user = "admin")
void
should_ThrowWorkbasketNotFoundException_When_TryingToSetAccessItemsOfNonExistingWorkbasket() {
final WorkbasketService workbasketService = taskanaEngine.getWorkbasketService();
WorkbasketAccessItem workbasketAccessItem =
workbasketService.newWorkbasketAccessItem("WBI:1337gibtEsNicht007", "newAccessIdForUpdate");
assertThatThrownBy(
() ->
workbasketService.setWorkbasketAccessItems(
"WBI:1337gibtEsNicht007", List.of(workbasketAccessItem)))
.isInstanceOf(WorkbasketNotFoundException.class);
}
@WithAccessId(user = "businessadmin") @WithAccessId(user = "businessadmin")
@Test @Test
void testUpdateWorkbasketAccessItemSucceeds() throws Exception { void testUpdateWorkbasketAccessItemSucceeds() throws Exception {

View File

@ -137,8 +137,7 @@ class WorkbasketServiceImplTest {
verify(taskanaEngine, times(2)).checkRoleMembership(any()); verify(taskanaEngine, times(2)).checkRoleMembership(any());
verify(internalTaskanaEngineMock, times(2)).getEngine(); verify(internalTaskanaEngineMock, times(2)).getEngine();
verify(internalTaskanaEngineMock, times(1)).domainExists(any()); verify(internalTaskanaEngineMock, times(1)).domainExists(any());
verify(distributionTargetMapperMock) verify(distributionTargetMapperMock).deleteAllDistributionTargetsBySourceId(expectedWb.getId());
.deleteAllDistributionTargetsBySourceId(expectedWb.getId());
verify(workbasketMapperMock).update(expectedWb); verify(workbasketMapperMock).update(expectedWb);
verify(internalTaskanaEngineMock, times(1)).getHistoryEventManager(); verify(internalTaskanaEngineMock, times(1)).getHistoryEventManager();

View File

@ -7,6 +7,7 @@ import org.springframework.transaction.annotation.Transactional;
import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException; import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException; import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.classification.api.exceptions.MalformedServiceLevelException;
import pro.taskana.classification.api.models.Classification; import pro.taskana.classification.api.models.Classification;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.DomainNotFoundException; import pro.taskana.common.api.exceptions.DomainNotFoundException;
@ -21,7 +22,6 @@ import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.ObjectReference; import pro.taskana.task.api.models.ObjectReference;
import pro.taskana.task.api.models.Task; import pro.taskana.task.api.models.Task;
import pro.taskana.workbasket.api.WorkbasketType; import pro.taskana.workbasket.api.WorkbasketType;
import pro.taskana.workbasket.api.exceptions.InvalidWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException; import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
@ -37,11 +37,11 @@ public class ExampleBootstrap {
@PostConstruct @PostConstruct
public void test() public void test()
throws TaskNotFoundException, NotAuthorizedException, WorkbasketNotFoundException, throws InvalidArgumentException, WorkbasketAlreadyExistException, DomainNotFoundException,
ClassificationNotFoundException, InvalidStateException, InvalidOwnerException, NotAuthorizedException, ClassificationAlreadyExistException,
TaskAlreadyExistException, InvalidArgumentException, DomainNotFoundException, MalformedServiceLevelException, TaskAlreadyExistException, WorkbasketNotFoundException,
InvalidWorkbasketException, WorkbasketAlreadyExistException, ClassificationNotFoundException, AttachmentPersistenceException, TaskNotFoundException,
ClassificationAlreadyExistException, AttachmentPersistenceException { InvalidOwnerException, InvalidStateException {
System.out.println("---------------------------> Start App"); System.out.println("---------------------------> Start App");
Workbasket wb = taskanaEngine.getWorkbasketService().newWorkbasket("workbasket", "DOMAIN_A"); Workbasket wb = taskanaEngine.getWorkbasketService().newWorkbasket("workbasket", "DOMAIN_A");

View File

@ -11,10 +11,10 @@ import org.springframework.web.bind.annotation.RestController;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.DomainNotFoundException; import pro.taskana.common.api.exceptions.DomainNotFoundException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException; import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.util.IdGenerator; import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.workbasket.api.WorkbasketType; import pro.taskana.workbasket.api.WorkbasketType;
import pro.taskana.workbasket.api.exceptions.InvalidWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.workbasket.api.models.Workbasket; import pro.taskana.workbasket.api.models.Workbasket;
import pro.taskana.workbasket.internal.models.WorkbasketImpl; import pro.taskana.workbasket.internal.models.WorkbasketImpl;
@ -51,7 +51,7 @@ public class TaskanaTestController {
@GetMapping(path = "/transaction") @GetMapping(path = "/transaction")
public @ResponseBody String transaction( public @ResponseBody String transaction(
@RequestParam(value = "rollback", defaultValue = "false") String rollback) @RequestParam(value = "rollback", defaultValue = "false") String rollback)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketAlreadyExistException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketAlreadyExistException,
DomainNotFoundException { DomainNotFoundException {
taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key", "workbasket")); taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key", "workbasket"));
@ -67,7 +67,7 @@ public class TaskanaTestController {
@GetMapping(path = "/transaction-many") @GetMapping(path = "/transaction-many")
public @ResponseBody String transactionMany( public @ResponseBody String transactionMany(
@RequestParam(value = "rollback", defaultValue = "false") String rollback) @RequestParam(value = "rollback", defaultValue = "false") String rollback)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketAlreadyExistException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketAlreadyExistException,
DomainNotFoundException { DomainNotFoundException {
taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key1", "workbasket1")); taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key1", "workbasket1"));
taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key2", "workbasket2")); taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key2", "workbasket2"));
@ -84,7 +84,7 @@ public class TaskanaTestController {
@GetMapping(path = "/customdb") @GetMapping(path = "/customdb")
public @ResponseBody String transactionCustomdb( public @ResponseBody String transactionCustomdb(
@RequestParam(value = "rollback", defaultValue = "false") String rollback) @RequestParam(value = "rollback", defaultValue = "false") String rollback)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketAlreadyExistException, throws InvalidArgumentException, NotAuthorizedException, WorkbasketAlreadyExistException,
DomainNotFoundException { DomainNotFoundException {
taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key1", "workbasket1")); taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key1", "workbasket1"));
taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key2", "workbasket2")); taskanaEngine.getWorkbasketService().createWorkbasket(createWorkBasket("key2", "workbasket2"));

View File

@ -209,13 +209,12 @@ class TaskanaTransactionIntTest {
taskCleanupJob.run(); taskCleanupJob.run();
ThrowingCallable httpCall = ThrowingCallable httpCall =
() -> { () ->
workbasketService.deleteWorkbasket( workbasketService.deleteWorkbasket(
workbasketService.getWorkbasket("key3", "DOMAIN_A").getId()); workbasketService.getWorkbasket("key3", "DOMAIN_A").getId());
};
assertThatThrownBy(httpCall) assertThatThrownBy(httpCall)
.isInstanceOf(WorkbasketInUseException.class) .isInstanceOf(WorkbasketInUseException.class)
.hasMessageContaining("contains 1 non-completed tasks"); .hasMessageContaining("contains non-completed Tasks");
WorkbasketCleanupJob job = WorkbasketCleanupJob job =
new WorkbasketCleanupJob(taskanaEngine, springTransactionProvider, null); new WorkbasketCleanupJob(taskanaEngine, springTransactionProvider, null);

View File

@ -1,13 +1,7 @@
package pro.taskana.example.jobs; package pro.taskana.example.jobs;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.security.auth.Subject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -16,8 +10,6 @@ import org.springframework.stereotype.Component;
import pro.taskana.common.api.ScheduledJob.Type; import pro.taskana.common.api.ScheduledJob.Type;
import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.security.UserPrincipal;
import pro.taskana.common.internal.jobs.JobRunner; import pro.taskana.common.internal.jobs.JobRunner;
import pro.taskana.common.internal.transaction.TaskanaTransactionProvider; import pro.taskana.common.internal.transaction.TaskanaTransactionProvider;
import pro.taskana.task.internal.jobs.TaskCleanupJob; import pro.taskana.task.internal.jobs.TaskCleanupJob;
@ -57,49 +49,18 @@ public class JobScheduler {
@Scheduled(cron = "${taskana.jobscheduler.async.cron}") @Scheduled(cron = "${taskana.jobscheduler.async.cron}")
public void triggerJobs() { public void triggerJobs() {
LOGGER.info("AsyncJobs started."); LOGGER.info("AsyncJobs started.");
try { runAsyncJobsAsAdmin();
runAsyncJobsAsAdmin(); LOGGER.info("AsyncJobs completed.");
LOGGER.info("AsyncJobs completed.");
} catch (PrivilegedActionException e) {
LOGGER.info("AsyncJobs failed.", e);
}
} }
/* private void runAsyncJobsAsAdmin() {
* Creates an admin subject and runs the job using the subject. taskanaEngine.runAsAdmin(
*/
private void runAsyncJobsAsAdmin() throws PrivilegedActionException {
PrivilegedExceptionAction<Object> jobs =
() -> { () -> {
try { JobRunner runner = new JobRunner(taskanaEngine);
JobRunner runner = new JobRunner(taskanaEngine); runner.registerTransactionProvider(springTransactionProvider);
runner.registerTransactionProvider(springTransactionProvider); LOGGER.info("Running Jobs");
LOGGER.info("Running Jobs"); runner.runJobs();
runner.runJobs(); return "Successful";
return "Successful"; });
} catch (Throwable e) {
throw new Exception(e);
}
};
Subject.doAs(getAdminSubject(), jobs);
}
private Subject getAdminSubject() {
Subject subject = new Subject();
List<Principal> principalList = new ArrayList<>();
try {
principalList.add(
new UserPrincipal(
taskanaEngine
.getConfiguration()
.getRoleMap()
.get(TaskanaRole.ADMIN)
.iterator()
.next()));
} catch (Throwable t) {
LOGGER.warn("Could not determine a configured admin user.", t);
}
subject.getPrincipals().addAll(principalList);
return subject;
} }
} }

View File

@ -27,7 +27,7 @@ class LdapEmptySearchRootsTest extends LdapTest {
} }
@Test @Test
void should_ReturnFullDnForUser_When_AccessIdOfUserIsGiven() { void should_ReturnFullDnForUser_When_AccessIdOfUserIsGiven() throws Exception {
String dn = ldapClient.searchDnForAccessId("otheruser"); String dn = ldapClient.searchDnForAccessId("otheruser");
assertThat(dn).isEqualTo("uid=otheruser,cn=other-users,ou=test,o=taskana"); assertThat(dn).isEqualTo("uid=otheruser,cn=other-users,ou=test,o=taskana");
} }

Some files were not shown because too many files have changed in this diff Show More