diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java b/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java new file mode 100644 index 000000000..181191aaf --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/HistoryEventProducer.java @@ -0,0 +1,57 @@ +package pro.taskana.history; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import pro.taskana.history.api.TaskanaHistory; +import pro.taskana.history.api.TaskanaHistoryEvent; + +/** + * Creates events and emits them to the registered history service providers. + */ +public final class HistoryEventProducer { + + private static final Logger LOGGER = LoggerFactory.getLogger(HistoryEventProducer.class); + + private static HistoryEventProducer emitterInstance; + private ServiceLoader serviceLoader; + private boolean enabled = false; + + public static synchronized HistoryEventProducer getInstance() { + if (emitterInstance == null) { + emitterInstance = new HistoryEventProducer(); + } + return emitterInstance; + } + + public static boolean isHistoryEnabled() { + return getInstance().isEnabled(); + } + + private HistoryEventProducer() { + serviceLoader = ServiceLoader.load(TaskanaHistory.class); + Iterator serviceIterator = serviceLoader.iterator(); + while (serviceIterator.hasNext()) { + TaskanaHistory history = serviceIterator.next(); + LOGGER.info("Registered history provider: {}", history.getClass().getName()); + enabled = true; + } + if (!enabled) { + LOGGER.info("No history provider found. Running without history."); + } + } + + public boolean isEnabled() { + return enabled; + } + + public void createEvent(TaskanaHistoryEvent event) { + LOGGER.debug("Sending event to history service providers: {}", event); + serviceLoader.forEach(historyProvider -> { + historyProvider.create(event); + }); + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskCompletionEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskCompletionEvent.java new file mode 100644 index 000000000..7aab982c2 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskCompletionEvent.java @@ -0,0 +1,17 @@ +package pro.taskana.history.api; + +import pro.taskana.Task; + +/** + * Event fired if a task is completed. + */ +public class TaskCompletionEvent extends TaskHistoryEvent { + + public TaskCompletionEvent(Task completedTask) { + super(completedTask); + type = "TASK_COMPLETED"; + created = completedTask.getCompleted(); + + } + +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskHistoryEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskHistoryEvent.java new file mode 100644 index 000000000..d65543131 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskHistoryEvent.java @@ -0,0 +1,147 @@ +package pro.taskana.history.api; + +import pro.taskana.Task; + +/** + * Super class for all task related events. + */ +public class TaskHistoryEvent extends TaskanaHistoryEvent { + + protected String taskId; + protected String businessProcessId; + protected String parentBusinessProcessId; + protected String domain; + protected String workbasketKey; + protected String taskClassificationCategory; + protected String taskClassificationKey; + protected String attachmentClassificationKey; + protected String porCompany; + protected String porSystem; + protected String porInstance; + protected String porType; + protected String porValue; + + public TaskHistoryEvent(Task task) { + super(); + taskId = task.getId(); + businessProcessId = task.getBusinessProcessId(); + parentBusinessProcessId = task.getParentBusinessProcessId(); + domain = task.getDomain(); + workbasketKey = task.getWorkbasketKey(); + taskClassificationCategory = task.getClassificationCategory(); + taskClassificationKey = task.getClassificationSummary().getKey(); + if (!task.getAttachments().isEmpty()) { + attachmentClassificationKey = task.getAttachments().get(0).getClassificationSummary().getKey(); + } + porCompany = task.getPrimaryObjRef().getCompany(); + porSystem = task.getPrimaryObjRef().getSystem(); + porInstance = task.getPrimaryObjRef().getSystemInstance(); + porType = task.getPrimaryObjRef().getType(); + porValue = task.getPrimaryObjRef().getValue(); + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public String getBusinessProcessId() { + return businessProcessId; + } + + public void setBusinessProcessId(String businessProcessId) { + this.businessProcessId = businessProcessId; + } + + public String getParentBusinessProcessId() { + return parentBusinessProcessId; + } + + public void setParentBusinessProcessId(String parentBusinessProcessId) { + this.parentBusinessProcessId = parentBusinessProcessId; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getWorkbasketKey() { + return workbasketKey; + } + + public void setWorkbasketKey(String workbasketKey) { + this.workbasketKey = workbasketKey; + } + + public String getTaskClassificationCategory() { + return taskClassificationCategory; + } + + public void setTaskClassificationCategory(String taskClassificationCategory) { + this.taskClassificationCategory = taskClassificationCategory; + } + + public String getTaskClassificationKey() { + return taskClassificationKey; + } + + public void setTaskClassificationKey(String taskClassificationKey) { + this.taskClassificationKey = taskClassificationKey; + } + + public String getAttachmentClassificationKey() { + return attachmentClassificationKey; + } + + public void setAttachmentClassificationKey(String attachmentClassificationKey) { + this.attachmentClassificationKey = attachmentClassificationKey; + } + + public String getPorCompany() { + return porCompany; + } + + public void setPorCompany(String porCompany) { + this.porCompany = porCompany; + } + + public String getPorSystem() { + return porSystem; + } + + public void setPorSystem(String porSystem) { + this.porSystem = porSystem; + } + + public String getPorInstance() { + return porInstance; + } + + public void setPorInstance(String porInstance) { + this.porInstance = porInstance; + } + + public String getPorType() { + return porType; + } + + public void setPorType(String porType) { + this.porType = porType; + } + + public String getPorValue() { + return porValue; + } + + public void setPorValue(String porValue) { + this.porValue = porValue; + } + +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistory.java b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistory.java new file mode 100644 index 000000000..5fae29357 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistory.java @@ -0,0 +1,16 @@ +package pro.taskana.history.api; + +/** + * Interface for TASKANA History Service Provider. + */ +public interface TaskanaHistory { + + /** + * Create a new history event. + * + * @param event + * {@link TaskanaHistoryEvent} The event to be created. + */ + void create(TaskanaHistoryEvent event); + +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java new file mode 100644 index 000000000..a67ac4cde --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/history/api/TaskanaHistoryEvent.java @@ -0,0 +1,62 @@ +package pro.taskana.history.api; + +import java.time.Instant; + +import pro.taskana.security.CurrentUserContext; + +/** + * Super class for all specific events from the TASKANA engine. + */ +public class TaskanaHistoryEvent { + + protected long id; + protected String type; + protected String userId; + protected Instant created; + protected String comment; + + public TaskanaHistoryEvent() { + userId = CurrentUserContext.getUserid(); + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public Instant getCreated() { + return created; + } + + public void setCreated(Instant created) { + this.created = created; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java index 5625915ae..f3b98578b 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskServiceImpl.java @@ -44,6 +44,8 @@ import pro.taskana.exceptions.TaskAlreadyExistException; import pro.taskana.exceptions.TaskNotFoundException; import pro.taskana.exceptions.TaskanaException; import pro.taskana.exceptions.WorkbasketNotFoundException; +import pro.taskana.history.HistoryEventProducer; +import pro.taskana.history.api.TaskCompletionEvent; import pro.taskana.impl.report.TimeIntervalColumnHeader; import pro.taskana.impl.util.IdGenerator; import pro.taskana.impl.util.LoggerUtils; @@ -69,6 +71,7 @@ public class TaskServiceImpl implements TaskService { private ClassificationServiceImpl classificationService; private TaskMapper taskMapper; private AttachmentMapper attachmentMapper; + private HistoryEventProducer historyEventProducer; TaskServiceImpl(TaskanaEngine taskanaEngine, TaskMapper taskMapper, AttachmentMapper attachmentMapper) { @@ -84,6 +87,7 @@ public class TaskServiceImpl implements TaskService { this.workbasketService = taskanaEngine.getWorkbasketService(); this.attachmentMapper = attachmentMapper; this.classificationService = (ClassificationServiceImpl) taskanaEngine.getClassificationService(); + this.historyEventProducer = ((TaskanaEngineImpl) taskanaEngine).getHistoryEventProducer(); } @Override @@ -219,6 +223,12 @@ public class TaskServiceImpl implements TaskService { task.setOwner(userId); taskMapper.update(task); LOGGER.debug("Task '{}' completed by user '{}'.", taskId, userId); + + if (HistoryEventProducer.isHistoryEnabled()) { + TaskCompletionEvent event = new TaskCompletionEvent(task); + historyEventProducer.createEvent(event); + } + } finally { taskanaEngine.returnConnection(); LOGGER.debug("exit from completeTask()"); @@ -618,8 +628,10 @@ public class TaskServiceImpl implements TaskService { } // filter taskSummaries and update values - taskSummaries = taskSummaries.stream().filter(ts -> taskIds.contains(ts.getTaskId())).collect( - Collectors.toList()); + taskSummaries = taskSummaries.stream() + .filter(ts -> taskIds.contains(ts.getTaskId())) + .collect( + Collectors.toList()); if (!taskSummaries.isEmpty()) { Instant now = Instant.now(); TaskSummaryImpl updateObject = new TaskSummaryImpl(); @@ -803,8 +815,10 @@ public class TaskServiceImpl implements TaskService { return new ArrayList<>(); } - Set classificationIdSet = taskSummaries.stream().map(t -> t.getClassificationSummary().getId()).collect( - Collectors.toSet()); + Set classificationIdSet = taskSummaries.stream() + .map(t -> t.getClassificationSummary().getId()) + .collect( + Collectors.toSet()); if (attachmentSummaries != null && !attachmentSummaries.isEmpty()) { for (AttachmentSummaryImpl att : attachmentSummaries) { @@ -845,8 +859,10 @@ public class TaskServiceImpl implements TaskService { return; } // calculate parameters for workbasket query: workbasket keys - Set workbasketIdSet = taskSummaries.stream().map(t -> t.getWorkbasketSummary().getId()).collect( - Collectors.toSet()); + Set workbasketIdSet = taskSummaries.stream() + .map(t -> t.getWorkbasketSummary().getId()) + .collect( + Collectors.toSet()); String[] workbasketIdArray = workbasketIdSet.toArray(new String[0]); // perform workbasket query LOGGER.debug("addWorkbasketSummariesToTaskSummaries() about to query workbaskets"); @@ -1500,8 +1516,10 @@ public class TaskServiceImpl implements TaskService { if (attachmentImpls == null || attachmentImpls.isEmpty()) { return result; } - Set classificationIds = attachmentImpls.stream().map(t -> t.getClassificationSummary().getId()).collect( - Collectors.toSet()); + Set classificationIds = attachmentImpls.stream() + .map(t -> t.getClassificationSummary().getId()) + .collect( + Collectors.toSet()); List classifications = classificationService.createClassificationQuery() .idIn(classificationIds.toArray(new String[0])) .list(); diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java index d48b5b896..b17964a05 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java @@ -34,6 +34,7 @@ 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; import pro.taskana.mappings.AttachmentMapper; @@ -61,11 +62,13 @@ public class TaskanaEngineImpl implements TaskanaEngine { protected SqlSessionManager sessionManager; protected ConnectionManagementMode mode = ConnectionManagementMode.PARTICIPATE; protected java.sql.Connection connection = null; + protected HistoryEventProducer historyEventProducer; protected TaskanaEngineImpl(TaskanaEngineConfiguration taskanaEngineConfiguration) { this.taskanaEngineConfiguration = taskanaEngineConfiguration; createTransactionFactory(taskanaEngineConfiguration.getUseManagedTransactions()); this.sessionManager = createSqlSessionManager(); + this.historyEventProducer = HistoryEventProducer.getInstance(); } public static TaskanaEngine createTaskanaEngine(TaskanaEngineConfiguration taskanaEngineConfiguration) { @@ -165,6 +168,10 @@ public class TaskanaEngineImpl implements TaskanaEngine { return this.taskanaEngineConfiguration; } + public HistoryEventProducer getHistoryEventProducer() { + return this.historyEventProducer; + } + /** * sets the connection management mode. * @@ -228,7 +235,6 @@ public class TaskanaEngineImpl implements TaskanaEngine { /** * Open the connection to the database. to be called at the begin of each Api call that accesses the database - * */ void openConnection() { initSqlSession();