Closes #2374: Deadlock when creating HistoryEvents from many connections simultaneously

-Write operations are now performed with the regular TaskanEngine and its' SqlSession and TransactionFactory which provides the needed transactionality and doesn't open multiple connections
This commit is contained in:
Jörg Heffner 2023-10-24 14:47:45 +02:00 committed by Jörg Heffner
parent 4e1325c254
commit 8b9ca2550e
18 changed files with 262 additions and 534 deletions

View File

@ -1,31 +0,0 @@
package pro.taskana.simplehistory;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.spi.history.api.TaskanaHistory;
/** The TaskanaHistoryEngine represents an overall set of all needed services. */
public interface TaskanaHistoryEngine {
/**
* The TaskanaHistory can be used for operations on all history events.
*
* @return the HistoryService
*/
TaskanaHistory getTaskanaHistoryService();
/**
* check whether the current user is member of one of the roles specified.
*
* @param roles The roles that are checked for membership of the current user
* @return true if the current user is a member of at least one of the specified groups
*/
boolean isUserInRole(TaskanaRole... roles);
/**
* Checks whether current user is member of any of the specified roles.
*
* @param roles The roles that are checked for membership of the current user
* @throws NotAuthorizedException If the current user is not member of any specified role
*/
void checkRoleMembership(TaskanaRole... roles) throws NotAuthorizedException;
}

View File

@ -2,7 +2,6 @@ package pro.taskana.simplehistory.impl;
import static pro.taskana.common.api.BaseQuery.toLowerCopy;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.session.RowBounds;
@ -12,6 +11,7 @@ import pro.taskana.classification.api.ClassificationCustomField;
import pro.taskana.common.api.TimeInterval;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryQuery;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryQueryColumnName;
import pro.taskana.spi.history.api.events.classification.ClassificationHistoryEvent;
@ -33,7 +33,7 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
private static final String SQL_EXCEPTION_MESSAGE =
"Method openConnection() could not open a connection to the database.";
private final TaskanaHistoryEngineImpl taskanaHistoryEngine;
private final InternalTaskanaEngine internalTaskanaEngine;
private final List<String> orderBy = new ArrayList<>();
private final List<String> orderColumns = new ArrayList<>();
@ -82,8 +82,8 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
private String[] custom7Like;
private String[] custom8Like;
public ClassificationHistoryQueryImpl(TaskanaHistoryEngineImpl internalTaskanaHistoryEngine) {
this.taskanaHistoryEngine = internalTaskanaHistoryEngine;
public ClassificationHistoryQueryImpl(InternalTaskanaEngine internalTaskanaEngine) {
this.internalTaskanaEngine = internalTaskanaEngine;
}
@Override
@ -415,16 +415,13 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
@Override
public List<ClassificationHistoryEvent> list() {
List<ClassificationHistoryEvent> result = new ArrayList<>();
List<ClassificationHistoryEvent> result;
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this);
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -432,15 +429,12 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
public List<ClassificationHistoryEvent> list(int offset, int limit) {
List<ClassificationHistoryEvent> result = new ArrayList<>();
try {
taskanaHistoryEngine.openConnection();
internalTaskanaEngine.openConnection();
RowBounds rowBounds = new RowBounds(offset, limit);
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_MAPPER, this, rowBounds);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this, rowBounds);
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -454,17 +448,14 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
this.addOrderCriteria(columnName.toString(), sortDirection);
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_VALUE_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_VALUE_MAPPER, this);
return result;
} finally {
this.orderBy.addAll(cacheOrderBy);
this.columnName = null;
this.orderColumns.remove(orderColumns.size() - 1);
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -473,15 +464,12 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
ClassificationHistoryEvent result = null;
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectOne(LINK_TO_MAPPER, this);
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectOne(LINK_TO_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -489,14 +477,11 @@ public class ClassificationHistoryQueryImpl implements ClassificationHistoryQuer
public long count() {
Long rowCount = null;
try {
taskanaHistoryEngine.openConnection();
rowCount = taskanaHistoryEngine.getSqlSession().selectOne(LINK_TO_COUNTER, this);
internalTaskanaEngine.openConnection();
rowCount = internalTaskanaEngine.getSqlSession().selectOne(LINK_TO_COUNTER, this);
return (rowCount == null) ? 0L : rowCount;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
return -1;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}

View File

@ -1,20 +1,27 @@
package pro.taskana.simplehistory.impl;
import java.sql.SQLException;
import java.lang.reflect.Field;
import java.time.Instant;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.TaskanaEngineImpl;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryEventMapper;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryQuery;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryQueryMapper;
import pro.taskana.simplehistory.impl.task.TaskHistoryEventMapper;
import pro.taskana.simplehistory.impl.task.TaskHistoryQuery;
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryEventMapper;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryQuery;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryQueryMapper;
import pro.taskana.spi.history.api.TaskanaHistory;
import pro.taskana.spi.history.api.events.classification.ClassificationHistoryEvent;
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
@ -27,110 +34,149 @@ import pro.taskana.user.internal.UserMapper;
public class SimpleHistoryServiceImpl implements TaskanaHistory {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleHistoryServiceImpl.class);
private TaskanaHistoryEngineImpl taskanaHistoryEngine;
private TaskHistoryEventMapper taskHistoryEventMapper;
private WorkbasketHistoryEventMapper workbasketHistoryEventMapper;
private ClassificationHistoryEventMapper classificationHistoryEventMapper;
private UserMapper userMapper;
private InternalTaskanaEngine internalTaskanaEngine;
public void initialize(TaskanaEngine taskanaEngine) {
this.taskanaHistoryEngine = getTaskanaEngine(taskanaEngine);
LOGGER.info(
"Simple history service implementation initialized with schemaName: {} ",
taskanaEngine.getConfiguration().getSchemaName());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Simple history service implementation initialized with schemaName: {} ",
taskanaEngine.getConfiguration().getSchemaName());
Field sessionManager = null;
try {
Field internalTaskanaEngineImpl =
TaskanaEngineImpl.class.getDeclaredField("internalTaskanaEngineImpl");
internalTaskanaEngineImpl.setAccessible(true);
this.internalTaskanaEngine =
(InternalTaskanaEngine) internalTaskanaEngineImpl.get(taskanaEngine);
sessionManager = TaskanaEngineImpl.class.getDeclaredField("sessionManager");
sessionManager.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new SystemException("SQL Session could not be retrieved. Aborting Startup");
} catch (IllegalAccessException e) {
throw new SystemException(e.getMessage());
}
try {
SqlSession sqlSession = (SqlSession) sessionManager.get(taskanaEngine);
if (!sqlSession
.getConfiguration()
.getMapperRegistry()
.hasMapper(TaskHistoryEventMapper.class)) {
sqlSession.getConfiguration().addMapper(TaskHistoryEventMapper.class);
}
if (!sqlSession
.getConfiguration()
.getMapperRegistry()
.hasMapper(WorkbasketHistoryEventMapper.class)) {
this.taskHistoryEventMapper =
this.taskanaHistoryEngine.getSqlSession().getMapper(TaskHistoryEventMapper.class);
this.workbasketHistoryEventMapper =
this.taskanaHistoryEngine.getSqlSession().getMapper(WorkbasketHistoryEventMapper.class);
this.classificationHistoryEventMapper =
this.taskanaHistoryEngine.getSqlSession().getMapper(ClassificationHistoryEventMapper.class);
this.userMapper = taskanaHistoryEngine.getSqlSession().getMapper(UserMapper.class);
sqlSession.getConfiguration().addMapper(WorkbasketHistoryEventMapper.class);
}
if (!sqlSession
.getConfiguration()
.getMapperRegistry()
.hasMapper(ClassificationHistoryEventMapper.class)) {
sqlSession.getConfiguration().addMapper(ClassificationHistoryEventMapper.class);
}
if (!sqlSession
.getConfiguration()
.getMapperRegistry()
.hasMapper(ClassificationHistoryQueryMapper.class)) {
sqlSession.getConfiguration().addMapper(ClassificationHistoryQueryMapper.class);
}
if (!sqlSession
.getConfiguration()
.getMapperRegistry()
.hasMapper(TaskHistoryQueryMapper.class)) {
sqlSession.getConfiguration().addMapper(TaskHistoryQueryMapper.class);
}
if (!sqlSession
.getConfiguration()
.getMapperRegistry()
.hasMapper(WorkbasketHistoryQueryMapper.class)) {
sqlSession.getConfiguration().addMapper(WorkbasketHistoryQueryMapper.class);
}
this.taskHistoryEventMapper = sqlSession.getMapper(TaskHistoryEventMapper.class);
this.workbasketHistoryEventMapper = sqlSession.getMapper(WorkbasketHistoryEventMapper.class);
this.classificationHistoryEventMapper =
sqlSession.getMapper(ClassificationHistoryEventMapper.class);
this.userMapper = sqlSession.getMapper(UserMapper.class);
} catch (IllegalAccessException e) {
throw new SystemException(
"TASKANA engine of Session Manager could not be retrieved. Aborting Startup");
}
}
@Override
public void create(TaskHistoryEvent event) {
try {
taskanaHistoryEngine.openConnection();
if (event.getCreated() == null) {
Instant now = Instant.now();
event.setCreated(now);
}
taskHistoryEventMapper.insert(event);
} catch (SQLException e) {
LOGGER.error("Error while inserting task history event into database", e);
} finally {
taskanaHistoryEngine.returnConnection();
if (event.getCreated() == null) {
Instant now = Instant.now();
event.setCreated(now);
}
taskHistoryEventMapper.insert(event);
}
@Override
public void create(WorkbasketHistoryEvent event) {
try {
taskanaHistoryEngine.openConnection();
if (event.getCreated() == null) {
Instant now = Instant.now();
event.setCreated(now);
}
workbasketHistoryEventMapper.insert(event);
} catch (SQLException e) {
LOGGER.error("Error while inserting workbasket history event into database", e);
} finally {
taskanaHistoryEngine.returnConnection();
if (event.getCreated() == null) {
Instant now = Instant.now();
event.setCreated(now);
}
workbasketHistoryEventMapper.insert(event);
}
@Override
public void create(ClassificationHistoryEvent event) {
try {
taskanaHistoryEngine.openConnection();
if (event.getCreated() == null) {
Instant now = Instant.now();
event.setCreated(now);
}
classificationHistoryEventMapper.insert(event);
} catch (SQLException e) {
LOGGER.error("Error while inserting classification history event into database", e);
} finally {
taskanaHistoryEngine.returnConnection();
if (event.getCreated() == null) {
Instant now = Instant.now();
event.setCreated(now);
}
classificationHistoryEventMapper.insert(event);
}
@Override
public void deleteHistoryEventsByTaskIds(List<String> taskIds)
throws InvalidArgumentException, NotAuthorizedException {
taskanaHistoryEngine.checkRoleMembership(TaskanaRole.ADMIN);
internalTaskanaEngine.openConnection();
internalTaskanaEngine.getEngine().checkRoleMembership(TaskanaRole.ADMIN);
if (taskIds == null) {
throw new InvalidArgumentException("List of taskIds must not be null.");
}
try {
taskanaHistoryEngine.openConnection();
taskHistoryEventMapper.deleteMultipleByTaskIds(taskIds);
} catch (SQLException e) {
LOGGER.error("Caught exception while trying to delete history events", e);
} finally {
taskanaHistoryEngine.returnConnection();
}
taskHistoryEventMapper.deleteMultipleByTaskIds(taskIds);
internalTaskanaEngine.returnConnection();
}
public TaskHistoryEvent getTaskHistoryEvent(String historyEventId)
throws TaskanaHistoryEventNotFoundException {
TaskHistoryEvent resultEvent = null;
try {
taskanaHistoryEngine.openConnection();
internalTaskanaEngine.openConnection();
resultEvent = taskHistoryEventMapper.findById(historyEventId);
if (resultEvent == null) {
throw new TaskanaHistoryEventNotFoundException(historyEventId);
}
if (taskanaHistoryEngine.getConfiguration().isAddAdditionalUserInfo()) {
if (internalTaskanaEngine.getEngine().getConfiguration().isAddAdditionalUserInfo()) {
User user = userMapper.findById(resultEvent.getUserId());
if (user != null) {
resultEvent.setUserLongName(user.getLongName());
@ -138,30 +184,20 @@ public class SimpleHistoryServiceImpl implements TaskanaHistory {
}
return resultEvent;
} catch (SQLException e) {
LOGGER.error("Caught exception while trying to retrieve a history event", e);
return resultEvent;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
public TaskHistoryQuery createTaskHistoryQuery() {
return new TaskHistoryQueryImpl(taskanaHistoryEngine);
return new TaskHistoryQueryImpl(internalTaskanaEngine);
}
public WorkbasketHistoryQuery createWorkbasketHistoryQuery() {
return new WorkbasketHistoryQueryImpl(taskanaHistoryEngine);
return new WorkbasketHistoryQueryImpl(internalTaskanaEngine);
}
public ClassificationHistoryQuery createClassificationHistoryQuery() {
return new ClassificationHistoryQueryImpl(taskanaHistoryEngine);
}
/*
* ATTENTION: This method exists for testing purposes.
*/
TaskanaHistoryEngineImpl getTaskanaEngine(TaskanaEngine taskanaEngine) {
return TaskanaHistoryEngineImpl.createTaskanaEngine(taskanaEngine);
return new ClassificationHistoryQueryImpl(internalTaskanaEngine);
}
}

View File

@ -2,7 +2,6 @@ package pro.taskana.simplehistory.impl;
import static pro.taskana.common.api.BaseQuery.toLowerCopy;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.session.RowBounds;
@ -10,6 +9,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.TimeInterval;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.simplehistory.impl.task.TaskHistoryQuery;
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryColumnName;
import pro.taskana.spi.history.api.events.task.TaskHistoryCustomField;
@ -30,7 +30,7 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
private static final String SQL_EXCEPTION_MESSAGE =
"Method openConnection() could not open a connection to the database.";
private final TaskanaHistoryEngineImpl taskanaHistoryEngine;
private final InternalTaskanaEngine internalTaskanaEngine;
private final List<String> orderBy;
private final List<String> orderColumns;
private boolean joinWithUserInfo;
@ -83,11 +83,12 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
private String[] custom3Like;
private String[] custom4Like;
public TaskHistoryQueryImpl(TaskanaHistoryEngineImpl taskanaHistoryEngine) {
this.taskanaHistoryEngine = taskanaHistoryEngine;
public TaskHistoryQueryImpl(InternalTaskanaEngine internalTaskanaEngine) {
this.internalTaskanaEngine = internalTaskanaEngine;
this.orderBy = new ArrayList<>();
this.orderColumns = new ArrayList<>();
this.joinWithUserInfo = taskanaHistoryEngine.getConfiguration().isAddAdditionalUserInfo();
this.joinWithUserInfo =
internalTaskanaEngine.getEngine().getConfiguration().isAddAdditionalUserInfo();
}
public String[] getIdIn() {
@ -631,14 +632,11 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
public List<TaskHistoryEvent> list() {
List<TaskHistoryEvent> result = new ArrayList<>();
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this);
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -646,15 +644,12 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
public List<TaskHistoryEvent> list(int offset, int limit) {
List<TaskHistoryEvent> result = new ArrayList<>();
try {
taskanaHistoryEngine.openConnection();
internalTaskanaEngine.openConnection();
RowBounds rowBounds = new RowBounds(offset, limit);
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_MAPPER, this, rowBounds);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this, rowBounds);
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -672,15 +667,12 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
}
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_VALUE_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_VALUE_MAPPER, this);
return result;
} finally {
this.orderColumns.remove(orderColumns.size() - 1);
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -688,16 +680,13 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
public TaskHistoryEvent single() {
TaskHistoryEvent result = null;
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectOne(LINK_TO_MAPPER, this);
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectOne(LINK_TO_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -705,14 +694,11 @@ public class TaskHistoryQueryImpl implements TaskHistoryQuery {
public long count() {
Long rowCount;
try {
taskanaHistoryEngine.openConnection();
rowCount = taskanaHistoryEngine.getSqlSession().selectOne(LINK_TO_COUNTER, this);
internalTaskanaEngine.openConnection();
rowCount = internalTaskanaEngine.getSqlSession().selectOne(LINK_TO_COUNTER, this);
return (rowCount == null) ? 0L : rowCount;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
return -1;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}

View File

@ -1,255 +0,0 @@
package pro.taskana.simplehistory.impl;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.SqlSessionManager;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.apache.ibatis.type.JdbcType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.OracleSqlSessionFactory;
import pro.taskana.common.internal.configuration.DB;
import pro.taskana.common.internal.persistence.InstantTypeHandler;
import pro.taskana.common.internal.persistence.MapTypeHandler;
import pro.taskana.common.internal.persistence.StringTypeHandler;
import pro.taskana.simplehistory.TaskanaHistoryEngine;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryEventMapper;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryQueryMapper;
import pro.taskana.simplehistory.impl.task.TaskHistoryEventMapper;
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryEventMapper;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryQueryMapper;
import pro.taskana.spi.history.api.TaskanaHistory;
import pro.taskana.user.internal.UserMapper;
/** This is the implementation of TaskanaHistoryEngine. */
public class TaskanaHistoryEngineImpl implements TaskanaHistoryEngine {
protected static final ThreadLocal<Deque<SqlSessionManager>> SESSION_STACK = new ThreadLocal<>();
private static final Logger LOGGER = LoggerFactory.getLogger(TaskanaHistoryEngineImpl.class);
private static final String DEFAULT = "default";
private final SqlSessionManager sessionManager;
private final TaskanaConfiguration taskanaConfiguration;
private final TaskanaEngine taskanaEngine;
private TransactionFactory transactionFactory;
private TaskanaHistory taskanaHistoryService;
protected TaskanaHistoryEngineImpl(TaskanaEngine taskanaEngine) {
this.taskanaConfiguration = taskanaEngine.getConfiguration();
this.taskanaEngine = taskanaEngine;
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
sessionManager = createSqlSessionManager();
}
public static TaskanaHistoryEngineImpl createTaskanaEngine(TaskanaEngine taskanaEngine) {
return new TaskanaHistoryEngineImpl(taskanaEngine);
}
@Override
public TaskanaHistory getTaskanaHistoryService() {
if (taskanaHistoryService == null) {
SimpleHistoryServiceImpl historyService = new SimpleHistoryServiceImpl();
historyService.initialize(taskanaEngine);
taskanaHistoryService = historyService;
}
return taskanaHistoryService;
}
public boolean isUserInRole(TaskanaRole... roles) {
if (!getConfiguration().isSecurityEnabled()) {
return true;
}
Set<String> rolesMembers =
Arrays.stream(roles)
.map(role -> getConfiguration().getRoleMap().get(role))
.collect(HashSet::new, Set::addAll, Set::addAll);
return taskanaEngine.getCurrentUserContext().getAccessIds().stream()
.anyMatch(rolesMembers::contains);
}
public void checkRoleMembership(TaskanaRole... roles) throws NotAuthorizedException {
if (!isUserInRole(roles)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Throwing NotAuthorizedException because accessIds {} are not member of roles {}",
taskanaEngine.getCurrentUserContext().getAccessIds(),
Arrays.toString(roles));
}
throw new NotAuthorizedException(taskanaEngine.getCurrentUserContext().getUserid(), roles);
}
}
public TaskanaConfiguration getConfiguration() {
return this.taskanaConfiguration;
}
protected SqlSessionManager createSqlSessionManager() {
Environment environment =
new Environment(DEFAULT, this.transactionFactory, taskanaConfiguration.getDataSource());
Configuration configuration = new Configuration(environment);
// set databaseId
DB db;
try (Connection con = taskanaConfiguration.getDataSource().getConnection()) {
db = DB.getDB(con);
configuration.setDatabaseId(db.dbProductId);
} catch (SQLException e) {
throw new SystemException("Could not open a connection to set the databaseId", e);
}
// register type handlers
if (DB.ORACLE == db) {
// Use NULL instead of OTHER when jdbcType is not specified for null values,
// otherwise oracle driver will chunck on null values
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.getTypeHandlerRegistry().register(String.class, new StringTypeHandler());
}
configuration.getTypeHandlerRegistry().register(new MapTypeHandler());
configuration.getTypeHandlerRegistry().register(Instant.class, new InstantTypeHandler());
configuration.getTypeHandlerRegistry().register(JdbcType.TIMESTAMP, new InstantTypeHandler());
// add mappers
configuration.addMapper(TaskHistoryEventMapper.class);
configuration.addMapper(TaskHistoryQueryMapper.class);
configuration.addMapper(WorkbasketHistoryEventMapper.class);
configuration.addMapper(WorkbasketHistoryQueryMapper.class);
configuration.addMapper(ClassificationHistoryEventMapper.class);
configuration.addMapper(ClassificationHistoryQueryMapper.class);
configuration.addMapper(UserMapper.class);
SqlSessionFactory localSessionFactory;
if (DB.ORACLE == db) {
localSessionFactory =
new SqlSessionFactoryBuilder() {
@Override
public SqlSessionFactory build(Configuration config) {
return new OracleSqlSessionFactory(config);
}
}.build(configuration);
} else {
localSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
}
return SqlSessionManager.newInstance(localSessionFactory);
}
protected static void pushSessionToStack(SqlSessionManager session) {
getSessionStack().push(session);
}
protected static void popSessionFromStack() {
Deque<SqlSessionManager> stack = getSessionStack();
if (!stack.isEmpty()) {
stack.pop();
}
}
/**
* 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
*/
protected static Deque<SqlSessionManager> getSessionStack() {
Deque<SqlSessionManager> stack = SESSION_STACK.get();
if (stack == null) {
stack = new ArrayDeque<>();
SESSION_STACK.set(stack);
}
return stack;
}
protected static SqlSessionManager getSessionFromStack() {
Deque<SqlSessionManager> stack = getSessionStack();
if (stack.isEmpty()) {
return null;
}
return stack.peek();
}
/**
* Open the connection to the database. to be called at the begin of each Api call that accesses
* the database
*
* @throws SQLException thrown if the connection could not be opened.
*/
void openConnection() throws SQLException {
initSqlSession();
this.sessionManager.getConnection().setSchema(taskanaConfiguration.getSchemaName());
}
/**
* Returns the database connection into the pool. In the case of nested calls, simply pops the
* latest session from the session stack. Closes the connection if the session stack is empty. In
* mode AUTOCOMMIT commits before the connection is closed. To be called at the end of each Api
* call that accesses the database
*/
void returnConnection() {
popSessionFromStack();
if (getSessionStack().isEmpty()
&& this.sessionManager != null
&& this.sessionManager.isManagedSessionStarted()) {
try {
this.sessionManager.commit();
} catch (Exception e) {
// ignore
}
this.sessionManager.close();
}
}
/** Initializes the SqlSessionManager. */
void initSqlSession() {
this.sessionManager.startManagedSession();
}
/**
* retrieve the SqlSession used by taskana.
*
* @return the myBatis SqlSession object used by taskana
*/
SqlSession getSqlSession() {
return this.sessionManager;
}
/**
* creates the MyBatis transaction factory.
*
* @param useManagedTransactions true if TASKANA should use a ManagedTransactionFactory.
*/
private void createTransactionFactory(boolean useManagedTransactions) {
if (useManagedTransactions) {
this.transactionFactory = new ManagedTransactionFactory();
} else {
this.transactionFactory = new JdbcTransactionFactory();
}
}
}

View File

@ -2,7 +2,6 @@ package pro.taskana.simplehistory.impl;
import static pro.taskana.common.api.BaseQuery.toLowerCopy;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.session.RowBounds;
@ -11,6 +10,7 @@ import org.slf4j.LoggerFactory;
import pro.taskana.common.api.TimeInterval;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryQuery;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryQueryColumnName;
import pro.taskana.spi.history.api.events.workbasket.WorkbasketHistoryEvent;
@ -30,7 +30,7 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
private static final String SQL_EXCEPTION_MESSAGE =
"Method openConnection() could not open a connection to the database.";
private final TaskanaHistoryEngineImpl taskanaHistoryEngine;
private final InternalTaskanaEngine internalTaskanaEngine;
private final List<String> orderColumns;
@SuppressWarnings("unused")
@ -71,8 +71,8 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
private String[] orgLevel3Like;
private String[] orgLevel4Like;
public WorkbasketHistoryQueryImpl(TaskanaHistoryEngineImpl internalTaskanaHistoryEngine) {
this.taskanaHistoryEngine = internalTaskanaHistoryEngine;
public WorkbasketHistoryQueryImpl(InternalTaskanaEngine internalTaskanaEngine) {
this.internalTaskanaEngine = internalTaskanaEngine;
this.orderBy = new ArrayList<>();
this.orderColumns = new ArrayList<>();
}
@ -475,14 +475,11 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
public List<WorkbasketHistoryEvent> list() {
List<WorkbasketHistoryEvent> result = new ArrayList<>();
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this);
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -490,15 +487,12 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
public List<WorkbasketHistoryEvent> list(int offset, int limit) {
List<WorkbasketHistoryEvent> result = new ArrayList<>();
try {
taskanaHistoryEngine.openConnection();
internalTaskanaEngine.openConnection();
RowBounds rowBounds = new RowBounds(offset, limit);
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_MAPPER, this, rowBounds);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this, rowBounds);
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -512,17 +506,14 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
this.addOrderCriteria(columnName.toString(), sortDirection);
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectList(LINK_TO_VALUE_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectList(LINK_TO_VALUE_MAPPER, this);
return result;
} finally {
this.orderBy = cacheOrderBy;
this.columnName = null;
this.orderColumns.remove(orderColumns.size() - 1);
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -530,15 +521,12 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
public WorkbasketHistoryEvent single() {
WorkbasketHistoryEvent result = null;
try {
taskanaHistoryEngine.openConnection();
result = taskanaHistoryEngine.getSqlSession().selectOne(LINK_TO_MAPPER, this);
internalTaskanaEngine.openConnection();
result = internalTaskanaEngine.getSqlSession().selectOne(LINK_TO_MAPPER, this);
return result;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
return result;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}
@ -546,14 +534,11 @@ public class WorkbasketHistoryQueryImpl implements WorkbasketHistoryQuery {
public long count() {
Long rowCount;
try {
taskanaHistoryEngine.openConnection();
rowCount = taskanaHistoryEngine.getSqlSession().selectOne(LINK_TO_COUNTER, this);
internalTaskanaEngine.openConnection();
rowCount = internalTaskanaEngine.getSqlSession().selectOne(LINK_TO_COUNTER, this);
return (rowCount == null) ? 0L : rowCount;
} catch (SQLException e) {
LOGGER.error(SQL_EXCEPTION_MESSAGE, e.getCause());
return -1;
} finally {
taskanaHistoryEngine.returnConnection();
internalTaskanaEngine.returnConnection();
}
}

View File

@ -25,32 +25,31 @@ import pro.taskana.common.internal.jobs.AbstractTaskanaJob;
import pro.taskana.common.internal.transaction.TaskanaTransactionProvider;
import pro.taskana.common.internal.util.CollectionUtil;
import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl;
import pro.taskana.simplehistory.impl.TaskanaHistoryEngineImpl;
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
public class HistoryCleanupJob extends AbstractTaskanaJob {
private static final Logger LOGGER = LoggerFactory.getLogger(HistoryCleanupJob.class);
private final TaskanaHistoryEngineImpl taskanaHistoryEngine =
TaskanaHistoryEngineImpl.createTaskanaEngine(taskanaEngineImpl);
private final boolean allCompletedSameParentBusiness =
taskanaEngineImpl
.getConfiguration()
.isSimpleHistoryCleanupJobAllCompletedSameParentBusiness();
private final Duration minimumAge =
taskanaEngineImpl.getConfiguration().getSimpleHistoryCleanupJobMinimumAge();
private final int batchSize =
taskanaEngineImpl.getConfiguration().getSimpleHistoryCleanupJobBatchSize();
private SimpleHistoryServiceImpl simpleHistoryService = null;
public HistoryCleanupJob(
TaskanaEngine taskanaEngine,
TaskanaTransactionProvider txProvider,
ScheduledJob scheduledJob) {
super(taskanaEngine, txProvider, scheduledJob, true);
if (simpleHistoryService == null) {
simpleHistoryService = new SimpleHistoryServiceImpl();
simpleHistoryService.initialize(taskanaEngine);
}
}
public static Duration getLockExpirationPeriod(TaskanaConfiguration taskanaConfiguration) {
@ -62,9 +61,6 @@ public class HistoryCleanupJob extends AbstractTaskanaJob {
Instant createdBefore = Instant.now().minus(minimumAge);
LOGGER.info("Running job to delete all history events created before ({})", createdBefore);
try {
SimpleHistoryServiceImpl simpleHistoryService =
(SimpleHistoryServiceImpl) taskanaHistoryEngine.getTaskanaHistoryService();
List<TaskHistoryEvent> historyEventCandidatesToClean =
simpleHistoryService
.createTaskHistoryQuery()
@ -181,9 +177,6 @@ public class HistoryCleanupJob extends AbstractTaskanaJob {
private int deleteEvents(List<String> taskIdsToDeleteHistoryEventsFor)
throws InvalidArgumentException, NotAuthorizedException {
SimpleHistoryServiceImpl simpleHistoryService =
(SimpleHistoryServiceImpl) taskanaHistoryEngine.getTaskanaHistoryService();
int deletedTasksCount =
(int)
simpleHistoryService

View File

@ -19,7 +19,6 @@ import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.common.test.config.DataSourceGenerator;
import pro.taskana.sampledata.SampleDataGenerator;
import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl;
import pro.taskana.simplehistory.impl.TaskanaHistoryEngineImpl;
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryEventMapper;
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryEventMapper;
@ -33,7 +32,6 @@ import pro.taskana.task.internal.models.ObjectReferenceImpl;
public abstract class AbstractAccTest {
protected static TaskanaConfiguration taskanaConfiguration;
protected static TaskanaHistoryEngineImpl taskanaHistoryEngine;
protected static TaskanaEngine taskanaEngine;
protected static SimpleHistoryServiceImpl historyService;
@ -116,7 +114,6 @@ public abstract class AbstractAccTest {
taskanaConfiguration = configuration;
taskanaEngine =
TaskanaEngine.buildTaskanaEngine(taskanaConfiguration, ConnectionManagementMode.AUTOCOMMIT);
taskanaHistoryEngine = TaskanaHistoryEngineImpl.createTaskanaEngine(taskanaEngine);
taskService = taskanaEngine.getTaskService();
historyService = new SimpleHistoryServiceImpl();
historyService.initialize(taskanaEngine);
@ -129,10 +126,10 @@ public abstract class AbstractAccTest {
protected TaskHistoryQueryMapper getHistoryQueryMapper()
throws NoSuchFieldException, IllegalAccessException {
Field sessionManagerField = TaskanaHistoryEngineImpl.class.getDeclaredField("sessionManager");
Field sessionManagerField = TaskanaEngineImpl.class.getDeclaredField("sessionManager");
sessionManagerField.setAccessible(true);
SqlSessionManager sqlSessionManager =
(SqlSessionManager) sessionManagerField.get(taskanaHistoryEngine);
(SqlSessionManager) sessionManagerField.get(taskanaEngine);
return sqlSessionManager.getMapper(TaskHistoryQueryMapper.class);
}
@ -160,32 +157,32 @@ public abstract class AbstractAccTest {
protected static WorkbasketHistoryEventMapper getWorkbasketHistoryEventMapper() {
try {
Field sessionManager = TaskanaHistoryEngineImpl.class.getDeclaredField("sessionManager");
Field sessionManager = TaskanaEngineImpl.class.getDeclaredField("sessionManager");
sessionManager.setAccessible(true);
SqlSessionManager manager = (SqlSessionManager) sessionManager.get(taskanaHistoryEngine);
SqlSessionManager manager = (SqlSessionManager) sessionManager.get(taskanaEngine);
return manager.getMapper(WorkbasketHistoryEventMapper.class);
} catch (Exception e) {
throw new JUnitException(
String.format(
"Could not extract %s from %s",
WorkbasketHistoryEventMapper.class, TaskanaHistoryEngineImpl.class));
WorkbasketHistoryEventMapper.class, TaskanaEngineImpl.class));
}
}
protected static ClassificationHistoryEventMapper getClassificationHistoryEventMapper() {
try {
Field sessionManager = TaskanaHistoryEngineImpl.class.getDeclaredField("sessionManager");
Field sessionManager = TaskanaEngineImpl.class.getDeclaredField("sessionManager");
sessionManager.setAccessible(true);
SqlSessionManager manager = (SqlSessionManager) sessionManager.get(taskanaHistoryEngine);
SqlSessionManager manager = (SqlSessionManager) sessionManager.get(taskanaEngine);
return manager.getMapper(ClassificationHistoryEventMapper.class);
} catch (Exception e) {
throw new JUnitException(
String.format(
"Could not extract %s from %s",
ClassificationHistoryEventMapper.class, TaskanaHistoryEngineImpl.class));
ClassificationHistoryEventMapper.class, TaskanaEngineImpl.class));
}
}

View File

@ -30,6 +30,9 @@ class CreateHistoryEventOnClassificationDeletionAccTest extends AbstractAccTest
final String classificationId = "CLI:200000000000000000000000000000000015";
taskService.createTaskQuery().list();
historyService.deleteHistoryEventsByTaskIds(List.of("test12"));
List<ClassificationHistoryEvent> events =
historyService
.createClassificationHistoryQuery()

View File

@ -26,10 +26,10 @@ import pro.taskana.testapi.builder.TaskBuilder;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.models.WorkbasketSummary;
@TaskanaIntegrationTest
@WithServiceProvider(
serviceProviderInterface = TaskanaHistory.class,
serviceProviders = SimpleHistoryServiceImpl.class)
@TaskanaIntegrationTest
@ExtendWith(JaasExtension.class)
class CreateHistoryEventOnTaskDeletionAccTest {
@TaskanaInject TaskanaEngine taskanaEngine;
@ -47,8 +47,6 @@ class CreateHistoryEventOnTaskDeletionAccTest {
@WithAccessId(user = "admin")
@BeforeAll
void setUp() throws Exception {
historyService = new SimpleHistoryServiceImpl();
historyService.initialize(taskanaEngine);
defaultClassificationSummary =
DefaultTestEntities.defaultTestClassification()
@ -60,11 +58,15 @@ class CreateHistoryEventOnTaskDeletionAccTest {
task2 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
task3 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
task4 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
historyService = new SimpleHistoryServiceImpl();
historyService.initialize(taskanaEngine);
}
@WithAccessId(user = "admin")
@Test
void should_CreateDeleteHistoryEvent_When_TaskIsDeleted() throws Exception {
historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId()));
taskService.deleteTask(task4.getId());

View File

@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.spi.history.api.events.classification.ClassificationHistoryEvent;
import pro.taskana.spi.history.api.events.classification.ClassificationHistoryEventType;
@ -24,13 +25,13 @@ class ClassificationHistoryQueryImplTest {
private ClassificationHistoryQueryImpl historyQueryImpl;
@Mock private TaskanaHistoryEngineImpl taskanaHistoryEngineMock;
@Mock private InternalTaskanaEngine internalTaskanaEngineMock;
@Mock private SqlSession sqlSessionMock;
@BeforeEach
void setup() {
historyQueryImpl = new ClassificationHistoryQueryImpl(taskanaHistoryEngineMock);
historyQueryImpl = new ClassificationHistoryQueryImpl(internalTaskanaEngineMock);
}
@Test
@ -40,9 +41,9 @@ class ClassificationHistoryQueryImplTest {
createHistoryEvent(
ClassificationHistoryEventType.CREATED.getName(), "admin", "someDetails"));
doNothing().when(taskanaHistoryEngineMock).openConnection();
doNothing().when(taskanaHistoryEngineMock).returnConnection();
when(taskanaHistoryEngineMock.getSqlSession()).thenReturn(sqlSessionMock);
doNothing().when(internalTaskanaEngineMock).openConnection();
doNothing().when(internalTaskanaEngineMock).returnConnection();
when(internalTaskanaEngineMock.getSqlSession()).thenReturn(sqlSessionMock);
when(sqlSessionMock.selectList(any(), any())).thenReturn(new ArrayList<>(returnList));
List<ClassificationHistoryEvent> result =

View File

@ -19,6 +19,7 @@ import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.simplehistory.impl.task.TaskHistoryEventMapper;
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
import pro.taskana.simplehistory.impl.workbasket.WorkbasketHistoryEventMapper;
@ -40,13 +41,12 @@ class SimpleHistoryServiceImplTest {
@Mock private WorkbasketHistoryEventMapper workbasketHistoryEventMapperMock;
@Mock private WorkbasketHistoryQueryMapper workbasketHistoryQueryMapperMock;
@Mock private TaskanaHistoryEngineImpl taskanaHistoryEngineMock;
@Mock private TaskanaConfiguration taskanaConfiguration;
@Mock private TaskanaEngine taskanaEngine;
@Mock private InternalTaskanaEngine internalTaskanaEngine;
@Mock private SqlSessionManager sqlSessionManagerMock;
@Mock private SqlSession sqlSessionMock;
@ -58,9 +58,7 @@ class SimpleHistoryServiceImplTest {
"wbKey1", "taskId1", "type1", "wbKey2", "someUserId", "someDetails");
cutSpy.create(expectedWb);
verify(taskanaHistoryEngineMock, times(1)).openConnection();
verify(taskHistoryEventMapperMock, times(1)).insert(expectedWb);
verify(taskanaHistoryEngineMock, times(1)).returnConnection();
assertThat(expectedWb.getCreated()).isNotNull();
}
@ -71,9 +69,7 @@ class SimpleHistoryServiceImplTest {
"wbKey1", WorkbasketHistoryEventType.CREATED.getName(), "someUserId", "someDetails");
cutSpy.create(expectedEvent);
verify(taskanaHistoryEngineMock, times(1)).openConnection();
verify(workbasketHistoryEventMapperMock, times(1)).insert(expectedEvent);
verify(taskanaHistoryEngineMock, times(1)).returnConnection();
assertThat(expectedEvent.getCreated()).isNotNull();
}
@ -84,20 +80,21 @@ class SimpleHistoryServiceImplTest {
AbstractAccTest.createTaskHistoryEvent(
"wbKey1", "taskId1", "type1", "wbKey2", "someUserId", "someDetails"));
when(taskanaHistoryEngineMock.getConfiguration()).thenReturn(taskanaConfiguration);
when(taskanaConfiguration.isAddAdditionalUserInfo()).thenReturn(false);
when(taskanaHistoryEngineMock.getSqlSession()).thenReturn(sqlSessionMock);
when(internalTaskanaEngine.getSqlSession()).thenReturn(sqlSessionMock);
when(sqlSessionMock.selectList(any(), any())).thenReturn(new ArrayList<>(returnList));
when(internalTaskanaEngine.getEngine()).thenReturn(taskanaEngine);
when(taskanaEngine.getConfiguration()).thenReturn(taskanaConfiguration);
final List<TaskHistoryEvent> result =
cutSpy.createTaskHistoryQuery().taskIdIn("taskId1").list();
verify(taskanaHistoryEngineMock, times(1)).openConnection();
verify(taskanaHistoryEngineMock, times(1)).getSqlSession();
verify(internalTaskanaEngine, times(1)).openConnection();
verify(internalTaskanaEngine, times(1)).getSqlSession();
verify(sqlSessionMock, times(1)).selectList(any(), any());
verify(taskanaHistoryEngineMock, times(1)).returnConnection();
verify(internalTaskanaEngine, times(1)).returnConnection();
assertThat(result).hasSize(returnList.size());
assertThat(result.get(0).getWorkbasketKey()).isEqualTo(returnList.get(0).getWorkbasketKey());
}
@ -108,16 +105,15 @@ class SimpleHistoryServiceImplTest {
returnList.add(
AbstractAccTest.createWorkbasketHistoryEvent(
"wbKey1", WorkbasketHistoryEventType.CREATED.getName(), "someUserId", "someDetails"));
when(taskanaHistoryEngineMock.getSqlSession()).thenReturn(sqlSessionMock);
when(sqlSessionMock.selectList(any(), any())).thenReturn(new ArrayList<>(returnList));
when(internalTaskanaEngine.getSqlSession()).thenReturn(sqlSessionMock);
final List<WorkbasketHistoryEvent> result =
cutSpy.createWorkbasketHistoryQuery().keyIn("wbKey1").list();
verify(taskanaHistoryEngineMock, times(1)).openConnection();
verify(taskanaHistoryEngineMock, times(1)).getSqlSession();
verify(internalTaskanaEngine, times(1)).openConnection();
verify(internalTaskanaEngine, times(1)).getSqlSession();
verify(sqlSessionMock, times(1)).selectList(any(), any());
verify(taskanaHistoryEngineMock, times(1)).returnConnection();
verify(internalTaskanaEngine, times(1)).returnConnection();
assertThat(result).hasSize(returnList.size());
assertThat(result.get(0).getKey()).isEqualTo(returnList.get(0).getKey());
}

View File

@ -16,6 +16,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import pro.taskana.common.api.TimeInterval;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.spi.history.api.events.workbasket.WorkbasketHistoryEvent;
import pro.taskana.spi.history.api.events.workbasket.WorkbasketHistoryEventType;
@ -24,7 +25,7 @@ import pro.taskana.spi.history.api.events.workbasket.WorkbasketHistoryEventType;
@ExtendWith(MockitoExtension.class)
class WorkbasketHistoryQueryImplTest {
@Mock private TaskanaHistoryEngineImpl taskanaHistoryEngineMock;
@Mock private InternalTaskanaEngine internalTaskanaEngineMock;
private WorkbasketHistoryQueryImpl historyQueryImpl;
@ -32,7 +33,7 @@ class WorkbasketHistoryQueryImplTest {
@BeforeEach
void setup() {
historyQueryImpl = new WorkbasketHistoryQueryImpl(taskanaHistoryEngineMock);
historyQueryImpl = new WorkbasketHistoryQueryImpl(internalTaskanaEngineMock);
}
@Test
@ -47,9 +48,9 @@ class WorkbasketHistoryQueryImplTest {
null));
TimeInterval interval = new TimeInterval(Instant.now().minusNanos(1000), Instant.now());
doNothing().when(taskanaHistoryEngineMock).openConnection();
doNothing().when(taskanaHistoryEngineMock).returnConnection();
when(taskanaHistoryEngineMock.getSqlSession()).thenReturn(sqlSessionMock);
doNothing().when(internalTaskanaEngineMock).openConnection();
doNothing().when(internalTaskanaEngineMock).returnConnection();
when(internalTaskanaEngineMock.getSqlSession()).thenReturn(sqlSessionMock);
when(sqlSessionMock.selectList(any(), any())).thenReturn(new ArrayList<>(returnList));
List<WorkbasketHistoryEvent> result =

View File

@ -2,7 +2,6 @@ package pro.taskana.simplehistory.rest;
import jakarta.servlet.http.HttpServletRequest;
import java.beans.ConstructorProperties;
import java.sql.SQLException;
import java.util.List;
import java.util.function.BiConsumer;
import org.springframework.beans.factory.annotation.Autowired;
@ -14,7 +13,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.common.api.BaseQuery.SortDirection;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
@ -35,19 +33,17 @@ import pro.taskana.spi.history.api.exceptions.TaskanaHistoryEventNotFoundExcepti
@RestController
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class TaskHistoryEventController {
private final SimpleHistoryServiceImpl simpleHistoryService;
private final TaskHistoryEventRepresentationModelAssembler assembler;
@Autowired
public TaskHistoryEventController(
TaskanaConfiguration taskanaConfiguration,
TaskanaEngine taskanaEngine,
SimpleHistoryServiceImpl simpleHistoryServiceImpl,
TaskHistoryEventRepresentationModelAssembler assembler)
throws SQLException {
TaskHistoryEventRepresentationModelAssembler assembler) {
this.simpleHistoryService = simpleHistoryServiceImpl;
this.simpleHistoryService.initialize(TaskanaEngine.buildTaskanaEngine(taskanaConfiguration));
this.simpleHistoryService.initialize(taskanaEngine);
this.assembler = assembler;
}

View File

@ -2,6 +2,7 @@ package pro.taskana.common.api;
import java.sql.SQLException;
import java.util.function.Supplier;
import org.apache.ibatis.transaction.TransactionFactory;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.classification.api.ClassificationService;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
@ -93,7 +94,7 @@ public interface TaskanaEngine {
*/
@SuppressWarnings("checkstyle:JavadocMethod")
static TaskanaEngine buildTaskanaEngine(TaskanaConfiguration configuration) throws SQLException {
return buildTaskanaEngine(configuration, ConnectionManagementMode.PARTICIPATE);
return buildTaskanaEngine(configuration, ConnectionManagementMode.PARTICIPATE, null);
}
/**
@ -108,7 +109,26 @@ public interface TaskanaEngine {
static TaskanaEngine buildTaskanaEngine(
TaskanaConfiguration configuration, ConnectionManagementMode connectionManagementMode)
throws SQLException {
return TaskanaEngineImpl.createTaskanaEngine(configuration, connectionManagementMode);
return buildTaskanaEngine(configuration, connectionManagementMode, null);
}
/**
* Builds an {@linkplain TaskanaEngine} based on {@linkplain TaskanaConfiguration},
* SqlConnectionMode and TransactionFactory.
*
* @param configuration complete taskanaConfig to build the engine
* @param connectionManagementMode connectionMode for the SqlSession
* @param transactionFactory the TransactionFactory
* @return a {@linkplain TaskanaEngineImpl}
* @throws SQLException when the db schema could not be initialized
*/
static TaskanaEngine buildTaskanaEngine(
TaskanaConfiguration configuration,
ConnectionManagementMode connectionManagementMode,
TransactionFactory transactionFactory)
throws SQLException {
return TaskanaEngineImpl.createTaskanaEngine(
configuration, connectionManagementMode, transactionFactory);
}
/**

View File

@ -112,7 +112,9 @@ public class TaskanaEngineImpl implements TaskanaEngine {
protected Connection connection;
protected TaskanaEngineImpl(
TaskanaConfiguration taskanaConfiguration, ConnectionManagementMode connectionManagementMode)
TaskanaConfiguration taskanaConfiguration,
ConnectionManagementMode connectionManagementMode,
TransactionFactory transactionFactory)
throws SQLException {
LOGGER.info(
"initializing TASKANA with this configuration: {} and this mode: {}",
@ -146,7 +148,11 @@ public class TaskanaEngineImpl implements TaskanaEngine {
currentUserContext =
new CurrentUserContextImpl(TaskanaConfiguration.shouldUseLowerCaseForAccessIds());
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
if (transactionFactory == null) {
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
} else {
this.transactionFactory = transactionFactory;
}
sessionManager = createSqlSessionManager();
initializeDbSchema(taskanaConfiguration);
@ -156,7 +162,8 @@ public class TaskanaEngineImpl implements TaskanaEngine {
new TaskanaConfiguration.Builder(this.taskanaConfiguration)
.jobSchedulerEnabled(false)
.build();
TaskanaEngine taskanaEngine = TaskanaEngine.buildTaskanaEngine(configuration, EXPLICIT);
TaskanaEngine taskanaEngine =
TaskanaEngine.buildTaskanaEngine(configuration, EXPLICIT, transactionFactory);
RealClock clock =
new RealClock(
this.taskanaConfiguration.getJobSchedulerInitialStartDelay(),
@ -186,9 +193,12 @@ public class TaskanaEngineImpl implements TaskanaEngine {
}
public static TaskanaEngine createTaskanaEngine(
TaskanaConfiguration taskanaConfiguration, ConnectionManagementMode connectionManagementMode)
TaskanaConfiguration taskanaConfiguration,
ConnectionManagementMode connectionManagementMode,
TransactionFactory transactionFactory)
throws SQLException {
return new TaskanaEngineImpl(taskanaConfiguration, connectionManagementMode);
return new TaskanaEngineImpl(
taskanaConfiguration, connectionManagementMode, transactionFactory);
}
@Override
@ -246,7 +256,11 @@ public class TaskanaEngineImpl implements TaskanaEngine {
connection.setAutoCommit(false);
connection.setSchema(taskanaConfiguration.getSchemaName());
mode = EXPLICIT;
sessionManager.startManagedSession(connection);
if (transactionFactory.getClass().getSimpleName().equals("SpringManagedTransactionFactory")) {
sessionManager.startManagedSession();
} else {
sessionManager.startManagedSession(connection);
}
} else if (this.connection != null) {
closeConnection();
}

View File

@ -10,9 +10,7 @@ public class SpringTaskanaEngineImpl extends TaskanaEngineImpl implements Spring
public SpringTaskanaEngineImpl(
TaskanaConfiguration taskanaConfiguration, ConnectionManagementMode mode)
throws SQLException {
super(taskanaConfiguration, mode);
this.transactionFactory = new SpringManagedTransactionFactory();
this.sessionManager = createSqlSessionManager();
super(taskanaConfiguration, mode, new SpringManagedTransactionFactory());
}
public static SpringTaskanaEngine createTaskanaEngine(

View File

@ -11,6 +11,7 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.internal.SpringTaskanaEngine;
import pro.taskana.common.internal.configuration.DbSchemaCreator;
import pro.taskana.sampledata.SampleDataGenerator;
@ -44,7 +45,7 @@ public class ExampleRestConfiguration {
@DependsOn("generateSampleData")
public TaskanaEngine getTaskanaEngine(TaskanaConfiguration taskanaConfiguration)
throws SQLException {
return TaskanaEngine.buildTaskanaEngine(taskanaConfiguration);
return SpringTaskanaEngine.buildTaskanaEngine(taskanaConfiguration);
}
// only required to let the adapter example connect to the same database