TSK-986: Extract DbproductName and DbproductId

This commit is contained in:
Benjamin Eckstein 2019-12-11 10:47:55 +01:00 committed by Mustapha Zorgati
parent 67a67388f1
commit 0d24a29f68
12 changed files with 161 additions and 103 deletions

View File

@ -0,0 +1,46 @@
package pro.taskana.configuration;
import pro.taskana.exceptions.UnsupportedDatabaseException;
/**
* Supported versions of databases.
*/
public enum DB {
H2("H2", "h2"),
DB2("DB2", "db2"),
POSTGRESS("PostgreSQL", "postgres");
public final String dbProductname;
public final String dbProductId;
DB(String dbProductname, String dbProductId) {
this.dbProductname = dbProductname;
this.dbProductId = dbProductId;
}
public static boolean isDb2(String dbProductName) {
return dbProductName.contains(DB2.dbProductname);
}
public static boolean isH2(String databaseProductName) {
return databaseProductName.contains(H2.dbProductname);
}
public static boolean isPostgreSQL(String databaseProductName) {
return POSTGRESS.dbProductname.equals(databaseProductName);
}
public static String getDatabaseProductId(String databaseProductName) {
if (isDb2(databaseProductName)) {
return DB2.dbProductId;
} else if (isH2(databaseProductName)) {
return H2.dbProductId;
} else if (isPostgreSQL(databaseProductName)) {
return POSTGRESS.dbProductId;
} else {
throw new UnsupportedDatabaseException(databaseProductName);
}
}
}

View File

@ -44,13 +44,13 @@ public class DbSchemaCreator {
}
private static String selectDbScriptFileName(String dbProductName) {
return "PostgreSQL".equals(dbProductName)
return DB.isPostgreSQL(dbProductName)
? DB_SCHEMA_POSTGRES
: "H2".equals(dbProductName) ? DB_SCHEMA : DB_SCHEMA_DB2;
: DB.isH2(dbProductName) ? DB_SCHEMA : DB_SCHEMA_DB2;
}
private static String selectDbSchemaDetectionScript(String dbProductName) {
return "PostgreSQL".equals(dbProductName) ? DB_SCHEMA_DETECTION_POSTGRES : DB_SCHEMA_DETECTION;
return DB.isPostgreSQL(dbProductName) ? DB_SCHEMA_DETECTION_POSTGRES : DB_SCHEMA_DETECTION;
}
/**

View File

@ -286,7 +286,7 @@ public class TaskanaEngineConfiguration {
try (Connection connection = dataSource.getConnection()) {
String databaseProductName = connection.getMetaData().getDatabaseProductName();
if (TaskanaEngineImpl.isPostgreSQL(databaseProductName)) {
if (DB.isPostgreSQL(databaseProductName)) {
this.schemaName = this.schemaName.toLowerCase();
} else {
this.schemaName = this.schemaName.toUpperCase();

View File

@ -1,14 +1,19 @@
package pro.taskana.exceptions;
import pro.taskana.security.CurrentUserContext;
/**
* This exception is used to communicate a not authorized user.
*/
public class NotAuthorizedException extends TaskanaException {
public NotAuthorizedException(String msg) {
super(msg + " - [CURRENT USER: {'" + CurrentUserContext.getUserid() + "'}]");
private final String currentUserId;
public NotAuthorizedException(String msg, String currentUserId) {
super(msg + " - [CURRENT USER: {'" + currentUserId + "'}]");
this.currentUserId = currentUserId;
}
public String getCurrentUserId() {
return currentUserId;
}
private static final long serialVersionUID = 21235L;

View File

@ -20,6 +20,7 @@ import pro.taskana.TaskSummary;
import pro.taskana.TaskanaRole;
import pro.taskana.TimeInterval;
import pro.taskana.WorkbasketPermission;
import pro.taskana.configuration.DB;
import pro.taskana.exceptions.InvalidArgumentException;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.NotAuthorizedToQueryWorkbasketException;
@ -706,23 +707,27 @@ public class TaskQueryImpl implements TaskQuery {
public TaskQuery orderByClassificationName(SortDirection sortDirection) {
joinWithClassifications = true;
addClassificationNameToSelectClauseForOrdering = true;
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? addOrderCriteria("CNAME", sortDirection)
: addOrderCriteria("c.NAME", sortDirection);
}
private String getDatabaseId() {
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId();
}
@Override
public TaskQuery orderByAttachmentClassificationName(SortDirection sortDirection) {
joinWithAttachments = true;
addAttachmentClassificationNameToSelectClauseForOrdering = true;
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? addOrderCriteria("ACNAME", sortDirection)
: addOrderCriteria("ac.NAME", sortDirection);
}
@Override
public TaskQuery orderByClassificationKey(SortDirection sortDirection) {
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? addOrderCriteria("TCLASSIFICATION_KEY", sortDirection)
: addOrderCriteria("t.CLASSIFICATION_KEY", sortDirection);
}
@ -811,7 +816,7 @@ public class TaskQueryImpl implements TaskQuery {
public TaskQuery orderByAttachmentClassificationKey(SortDirection sortDirection) {
joinWithAttachments = true;
addAttachmentColumnsToSelectClauseForOrdering = true;
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? addOrderCriteria("ACLASSIFICATION_KEY", sortDirection)
: addOrderCriteria("a.CLASSIFICATION_KEY", sortDirection);
}
@ -820,7 +825,7 @@ public class TaskQueryImpl implements TaskQuery {
public TaskQuery orderByAttachmentClassificationId(SortDirection sortDirection) {
joinWithAttachments = true;
addAttachmentColumnsToSelectClauseForOrdering = true;
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? addOrderCriteria("ACLASSIFICATION_ID", sortDirection)
: addOrderCriteria("a.CLASSIFICATION_ID", sortDirection);
}
@ -984,15 +989,13 @@ public class TaskQueryImpl implements TaskQuery {
}
public String getLinkToMapperScript() {
return this.taskanaEngine
.getSqlSession()
.getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? LINK_TO_MAPPER_DB2
: LINK_TO_MAPPER;
}
public String getLinkToCounterTaskScript() {
return this.taskanaEngine.getSqlSession().getConfiguration().getDatabaseId().equals("db2")
return DB.DB2.dbProductId.equals(getDatabaseId())
? LINK_TO_COUNTER_DB2
: LINK_TO_COUNTER;
}

View File

@ -277,7 +277,8 @@ public class TaskServiceImpl implements TaskService {
if (workbaskets.isEmpty()) {
String currentUser = CurrentUserContext.getUserid();
throw new NotAuthorizedException(
"The current user " + currentUser + " has no read permission for workbasket " + workbasketId);
"The current user " + currentUser + " has no read permission for workbasket " + workbasketId,
CurrentUserContext.getUserid());
} else {
resultTask.setWorkbasketSummary(workbaskets.get(0));
}
@ -354,7 +355,7 @@ public class TaskServiceImpl implements TaskService {
@Override
public BulkOperationResults<String, TaskanaException> transferTasks(String destinationWorkbasketKey,
String destinationWorkbasketDomain, List<String> taskIds)
throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException {
throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException {
return taskTransferrer.transferTasks(destinationWorkbasketKey, destinationWorkbasketDomain, taskIds);
}
@ -442,7 +443,8 @@ public class TaskServiceImpl implements TaskService {
}
@Override
public BulkOperationResults<String, TaskanaException> setCallbackStateForTasks(List<String> externalIds, CallbackState state) {
public BulkOperationResults<String, TaskanaException> setCallbackStateForTasks(List<String> externalIds,
CallbackState state) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("entry to setCallbackStateForTasks(externalIds = {})", LoggerUtils.listToString(externalIds));
}
@ -502,7 +504,6 @@ public class TaskServiceImpl implements TaskService {
LOGGER.debug("exit from removeSingleTask()");
}
private void removeSingleTaskForCallbackStateByExternalId(BulkOperationResults<String,
TaskanaException> bulkLog,
List<MinimalTaskSummary> taskSummaries, Iterator<String> externalIdIterator, CallbackState desiredCallbackState) {
@ -1196,7 +1197,7 @@ public class TaskServiceImpl implements TaskService {
private void standardUpdateActions(TaskImpl oldTaskImpl, TaskImpl newTaskImpl,
PrioDurationHolder prioDurationFromAttachments)
throws InvalidArgumentException, ConcurrencyException, ClassificationNotFoundException {
throws InvalidArgumentException, ConcurrencyException, ClassificationNotFoundException {
validateObjectReference(newTaskImpl.getPrimaryObjRef(), "primary ObjectReference", "Task");
//TODO: not safe to rely only on different timestamps.
// With fast execution below 1ms there will be no concurrencyException
@ -1231,7 +1232,7 @@ public class TaskServiceImpl implements TaskService {
private void updateClassificationRelatedProperties(TaskImpl oldTaskImpl, TaskImpl newTaskImpl,
PrioDurationHolder prioDurationFromAttachments)
throws ClassificationNotFoundException {
throws ClassificationNotFoundException {
LOGGER.debug("entry to updateClassificationRelatedProperties()");
// insert Classification specifications if Classification is given.
ClassificationSummary oldClassificationSummary = oldTaskImpl.getClassificationSummary();
@ -1428,7 +1429,7 @@ public class TaskServiceImpl implements TaskService {
throw new AttachmentPersistenceException(
"Cannot insert the Attachement " + attachmentImpl.getId() + " for Task "
+ newTaskImpl.getId() + " because it already exists.",
e.getCause());
e.getCause());
}
LOGGER.debug("exit from handleNewAttachmentOnTaskUpdate(), returning {}", prioDuration);
return prioDuration;
@ -1713,7 +1714,7 @@ public class TaskServiceImpl implements TaskService {
String id = att.getClassificationSummary().getId();
bulkLog.addError(att.getClassificationSummary().getId(), new ClassificationNotFoundException(id,
"When processing task updates due to change of classification, the classification with id " + id
+ WAS_NOT_FOUND2));
+ WAS_NOT_FOUND2));
} else {
att.setClassificationSummary(classificationSummary);
result.add(att);
@ -1737,7 +1738,8 @@ public class TaskServiceImpl implements TaskService {
throw new InvalidStateException("Cannot delete Task " + taskId + " because it is not completed.");
}
if (CallbackState.CALLBACK_PROCESSING_REQUIRED.equals(task.getCallbackState())) {
throw new InvalidStateException("Task " + taskId + " cannot be deleted because its callback is not yet processed");
throw new InvalidStateException(
"Task " + taskId + " cannot be deleted because its callback is not yet processed");
}
taskMapper.delete(taskId);
@ -1750,7 +1752,7 @@ public class TaskServiceImpl implements TaskService {
private void createTasksCompletedEvents(List<TaskSummary> taskSummaries) {
taskSummaries.stream().forEach(task -> historyEventProducer.createEvent(new CompletedEvent(task))
);
);
}
List<TaskSummary> augmentTaskSummariesByContainedSummaries(List<TaskSummaryImpl> taskSummaries) {

View File

@ -28,11 +28,13 @@ import pro.taskana.history.HistoryEventProducer;
import pro.taskana.history.events.task.TransferredEvent;
import pro.taskana.impl.util.LoggerUtils;
import pro.taskana.mappings.TaskMapper;
import pro.taskana.security.CurrentUserContext;
/**
* This class is responsible for the transfer of tasks.
*/
public class TaskTransferrer {
private static final String WAS_NOT_FOUND2 = " was not found.";
private static final String CANNOT_BE_TRANSFERRED = " cannot be transferred.";
private static final String COMPLETED_TASK_WITH_ID = "Completed task with id ";
@ -160,7 +162,7 @@ public class TaskTransferrer {
BulkOperationResults<String, TaskanaException> transferTasks(String destinationWorkbasketKey,
String destinationWorkbasketDomain, List<String> taskIds)
throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException {
throws NotAuthorizedException, InvalidArgumentException, WorkbasketNotFoundException {
try {
taskanaEngine.openConnection();
if (LOGGER.isDebugEnabled()) {
@ -218,7 +220,7 @@ public class TaskTransferrer {
private BulkOperationResults<String, TaskanaException> transferTasks(List<String> taskIdsToBeTransferred,
Workbasket destinationWorkbasket)
throws InvalidArgumentException, WorkbasketNotFoundException, NotAuthorizedException {
throws InvalidArgumentException, WorkbasketNotFoundException, NotAuthorizedException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("entry to transferTasks(taskIdsToBeTransferred = {}, destinationWorkbasket = {})",
LoggerUtils.listToString(taskIdsToBeTransferred), destinationWorkbasket);
@ -305,7 +307,8 @@ public class TaskTransferrer {
.noneMatch(wb -> taskSummary.getWorkbasketId().equals(wb.getId()))) {
bulkLog.addError(currentTaskId,
new NotAuthorizedException(
"The workbasket of this task got not TRANSFER permissions. TaskId=" + currentTaskId));
"The workbasket of this task got not TRANSFER permissions. TaskId=" + currentTaskId,
CurrentUserContext.getUserid()));
taskIdIterator.remove();
}
}

View File

@ -29,12 +29,12 @@ import pro.taskana.TaskService;
import pro.taskana.TaskanaEngine;
import pro.taskana.TaskanaRole;
import pro.taskana.WorkbasketService;
import pro.taskana.configuration.DB;
import pro.taskana.configuration.TaskanaEngineConfiguration;
import pro.taskana.exceptions.AutocommitFailedException;
import pro.taskana.exceptions.ConnectionNotSetException;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.SystemException;
import pro.taskana.exceptions.UnsupportedDatabaseException;
import pro.taskana.history.HistoryEventProducer;
import pro.taskana.impl.persistence.MapTypeHandler;
import pro.taskana.impl.util.LoggerUtils;
@ -58,7 +58,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
private static final String DEFAULT = "default";
private static final Logger LOGGER = LoggerFactory.getLogger(TaskanaEngineImpl.class);
private static ThreadLocal<Deque<SqlSessionManager>> sessionStack = new ThreadLocal<>();
private static SessionStack sessionStack = new SessionStack();
protected TaskanaEngineConfiguration taskanaEngineConfiguration;
protected TransactionFactory transactionFactory;
protected SqlSessionManager sessionManager;
@ -81,58 +81,6 @@ public class TaskanaEngineImpl implements TaskanaEngine {
return new TaskanaEngineImpl(taskanaEngineConfiguration);
}
/**
* With sessionStack, we maintain a Stack of SqlSessionManager objects on a per thread basis. SqlSessionManager is
* the MyBatis object that wraps database connections. The purpose of this stack is to keep track of nested calls.
* Each external API call is wrapped into taskanaEngineImpl.openConnection(); .....
* taskanaEngineImpl.returnConnection(); calls. In order to avoid duplicate opening / closing of connections, we use
* the sessionStack in the following way: Each time, an openConnection call is received, we push the current
* sessionManager onto the stack. On the first call to openConnection, we call sessionManager.startManagedSession()
* to open a database connection. On each call to returnConnection() we pop one instance of sessionManager from the
* stack. When the stack becomes empty, we close the database connection by calling sessionManager.close()
*
* @return Stack of SqlSessionManager
*/
private static Deque<SqlSessionManager> getSessionStack() {
Deque<SqlSessionManager> stack = sessionStack.get();
if (stack == null) {
stack = new ArrayDeque<>();
sessionStack.set(stack);
}
return stack;
}
private static SqlSessionManager getSessionFromStack() {
Deque<SqlSessionManager> stack = getSessionStack();
if (stack.isEmpty()) {
return null;
}
return stack.peek();
}
private static void pushSessionToStack(SqlSessionManager session) {
getSessionStack().push(session);
}
private static void popSessionFromStack() {
Deque<SqlSessionManager> stack = getSessionStack();
if (!stack.isEmpty()) {
stack.pop();
}
}
public static boolean isDb2(String dbProductName) {
return dbProductName.contains("DB2");
}
public static boolean isH2(String databaseProductName) {
return databaseProductName.contains("H2");
}
public static boolean isPostgreSQL(String databaseProductName) {
return "PostgreSQL".equals(databaseProductName);
}
@Override
public TaskService getTaskService() {
SqlSession session = this.sessionManager;
@ -226,7 +174,8 @@ public class TaskanaEngineImpl implements TaskanaEngine {
accessIds,
rolesAsString);
}
throw new NotAuthorizedException("current user is not member of role(s) " + Arrays.toString(roles));
throw new NotAuthorizedException("current user is not member of role(s) " + Arrays.toString(roles),
CurrentUserContext.getUserid());
}
}
@ -265,15 +214,8 @@ public class TaskanaEngineImpl implements TaskanaEngine {
String databaseProductName;
try (Connection con = taskanaEngineConfiguration.getDatasource().getConnection()) {
databaseProductName = con.getMetaData().getDatabaseProductName();
if (isDb2(databaseProductName)) {
configuration.setDatabaseId("db2");
} else if (isH2(databaseProductName)) {
configuration.setDatabaseId("h2");
} else if (isPostgreSQL(databaseProductName)) {
configuration.setDatabaseId("postgres");
} else {
throw new UnsupportedDatabaseException(databaseProductName);
}
String databaseProductId = DB.getDatabaseProductId(databaseProductName);
configuration.setDatabaseId(databaseProductId);
} catch (SQLException e) {
throw new SystemException(
@ -326,7 +268,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
e.getCause());
}
if (mode != ConnectionManagementMode.EXPLICIT) {
pushSessionToStack(sessionManager);
sessionStack.pushSessionToStack(sessionManager);
}
}
@ -342,8 +284,8 @@ public class TaskanaEngineImpl implements TaskanaEngine {
@Override
public void returnConnection() {
if (mode != ConnectionManagementMode.EXPLICIT) {
popSessionFromStack();
if (getSessionStack().isEmpty()
sessionStack.popSessionFromStack();
if (sessionStack.getSessionStack().isEmpty()
&& sessionManager != null && sessionManager.isManagedSessionStarted()) {
if (mode == ConnectionManagementMode.AUTOCOMMIT) {
try {
@ -394,4 +336,51 @@ public class TaskanaEngineImpl implements TaskanaEngine {
}
}
/**
* With sessionStack, we maintain a Stack of SqlSessionManager objects on a per thread basis. SqlSessionManager is
* the MyBatis object that wraps database connections. The purpose of this stack is to keep track of nested calls.
* Each external API call is wrapped into taskanaEngineImpl.openConnection(); .....
* taskanaEngineImpl.returnConnection(); calls. In order to avoid duplicate opening / closing of connections, we use
* the sessionStack in the following way: Each time, an openConnection call is received, we push the current
* sessionManager onto the stack. On the first call to openConnection, we call sessionManager.startManagedSession()
* to open a database connection. On each call to returnConnection() we pop one instance of sessionManager from the
* stack. When the stack becomes empty, we close the database connection by calling sessionManager.close().
*/
private static class SessionStack {
private ThreadLocal<Deque<SqlSessionManager>> sessionStack = new ThreadLocal<>();
/**
*
* @return Stack of SqlSessionManager
*/
private Deque<SqlSessionManager> getSessionStack() {
Deque<SqlSessionManager> stack = sessionStack.get();
if (stack == null) {
stack = new ArrayDeque<>();
sessionStack.set(stack);
}
return stack;
}
private SqlSessionManager getSessionFromStack() {
Deque<SqlSessionManager> stack = getSessionStack();
if (stack.isEmpty()) {
return null;
}
return stack.peek();
}
private void pushSessionToStack(SqlSessionManager session) {
getSessionStack().push(session);
}
private void popSessionFromStack() {
Deque<SqlSessionManager> stack = getSessionStack();
if (!stack.isEmpty()) {
stack.pop();
}
}
}
}

View File

@ -286,7 +286,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
throw new NotAuthorizedException(
"Not authorized. Permission '" + Arrays.toString(requestedPermissions) + "' on workbasket '"
+ workbasketId
+ "' is needed.");
+ "' is needed.", CurrentUserContext.getUserid());
}
List<WorkbasketPermission> grantedPermissions = this.getPermissionsFromWorkbasketAccessItem(wbAcc);
@ -296,7 +296,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
isAuthorized = false;
throw new NotAuthorizedException(
"Not authorized. Permission '" + perm.name() + "' on workbasket '" + workbasketId
+ "' is needed.");
+ "' is needed.", CurrentUserContext.getUserid());
}
}
} finally {
@ -328,7 +328,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
"Not authorized. Permission '" + Arrays.toString(requestedPermissions)
+ "' on workbasket with key '"
+ workbasketKey
+ "' and domain '" + domain + "' is needed.");
+ "' and domain '" + domain + "' is needed.", CurrentUserContext.getUserid());
}
List<WorkbasketPermission> grantedPermissions = this.getPermissionsFromWorkbasketAccessItem(wbAcc);
@ -337,7 +337,7 @@ public class WorkbasketServiceImpl implements WorkbasketService {
isAuthorized = false;
throw new NotAuthorizedException(
"Not authorized. Permission '" + perm.name() + "' on workbasket with key '" + workbasketKey
+ "' and domain '" + domain + "' is needed.");
+ "' and domain '" + domain + "' is needed.", CurrentUserContext.getUserid());
}
}
} finally {

View File

@ -36,6 +36,14 @@ class ArchitectureTest {
myRule.check(importedClasses);
}
@Test
void onlyExceptionsShouldResideInExceptionPackage() {
ArchRule myRule = classes()
.that().resideInAPackage("..exceptions")
.should().beAssignableTo(Throwable.class);
myRule.check(importedClasses);
}
@Test
@Disabled
void noClassShouldThrowGenericException() {

View File

@ -18,6 +18,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import pro.taskana.TaskState;
import pro.taskana.TaskSummary;
import pro.taskana.TaskanaEngine;
import pro.taskana.configuration.DB;
/**
* Test for TaskQueryImpl.
@ -44,7 +45,7 @@ class TaskQueryImplTest {
when(taskanaEngine.getTaskService()).thenReturn(taskServiceMock);
Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setDatabaseId("h2");
configuration.setDatabaseId(DB.H2.dbProductId);
when(internalTaskanaEngine.getSqlSession()).thenReturn(sqlSession);
when(sqlSession.getConfiguration()).thenReturn(configuration);

View File

@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import pro.taskana.TaskanaSpringBootTest;
import pro.taskana.configuration.DB;
import pro.taskana.configuration.SpringTaskanaEngineConfiguration;
import pro.taskana.exceptions.SystemException;
import pro.taskana.sampledata.SampleDataGenerator;
@ -32,7 +33,7 @@ class TestSchemaNameCustomizable {
void resetDb() {
SampleDataGenerator sampleDataGenerator;
try {
if ("PostgreSQL".equals(dataSource.getConnection().getMetaData().getDatabaseProductName())) {
if (DB.POSTGRESS.dbProductname.equals(dataSource.getConnection().getMetaData().getDatabaseProductName())) {
isPostgres = true;
schemaName = schemaName.toLowerCase();
}