use of io.lumeer.engine.api.event.CreateDocument in project engine by Lumeer.
the class AssigneeChangeDetector method detectChanges.
@Override
public void detectChanges(final DocumentEvent documentEvent, final Collection collection) {
final CollectionPurpose purpose = collection.getPurpose();
final String assigneeAttr = purpose.getAssigneeAttributeId();
final boolean doneState = isDoneState(documentEvent, collection);
if (StringUtils.isNotEmpty(assigneeAttr) && isAttributeChanged(documentEvent, assigneeAttr)) {
if (!(documentEvent instanceof CreateDocument)) {
// delete previous due date and assignee events on the document
delayedActionDao.deleteScheduledActions(getResourcePath(documentEvent), Set.of(NotificationType.DUE_DATE_SOON, NotificationType.PAST_DUE_DATE, NotificationType.TASK_ASSIGNED, NotificationType.TASK_REOPENED, NotificationType.DUE_DATE_CHANGED));
if (!(documentEvent instanceof RemoveDocument) && !doneState) {
final ZonedDateTime dueDate = getDueDate(documentEvent, collection);
if (dueDate != null) {
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.PAST_DUE_DATE, dueDate));
if (dueDate.minus(DUE_DATE_SOON_DAYS, ChronoUnit.DAYS).isAfter(ZonedDateTime.now())) {
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.DUE_DATE_SOON, dueDate.minus(DUE_DATE_SOON_DAYS, ChronoUnit.DAYS)));
}
}
}
}
if (documentEvent instanceof UpdateDocument) {
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_UNASSIGNED, nowPlus(), getRemovedAssignees(documentEvent, collection)));
}
if (!(documentEvent instanceof RemoveDocument) && !doneState) {
// create new due date events on the document
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_ASSIGNED, nowPlus(), getAddedAssignees(documentEvent, collection)));
}
}
}
use of io.lumeer.engine.api.event.CreateDocument in project engine by Lumeer.
the class SingleStage method commitDocumentOperations.
private List<Document> commitDocumentOperations(final List<DocumentOperation> operations, final List<Document> createdDocuments, final Map<String, Collection> collectionsMapForCreatedDocuments) {
if (operations.isEmpty() && collectionsMapForCreatedDocuments.isEmpty()) {
return List.of();
}
final FunctionFacade functionFacade = task.getFunctionFacade();
final TaskProcessingFacade taskProcessingFacade = task.getTaskProcessingFacade(taskExecutor, functionFacade);
final PurposeChangeProcessor purposeChangeProcessor = task.getPurposeChangeProcessor();
// Collection -> [Document]
final Map<String, List<Document>> updatedDocuments = new HashMap<>();
Map<String, Set<String>> documentIdsByCollection = operations.stream().map(Operation::getEntity).collect(Collectors.groupingBy(Document::getCollectionId, mapping(Document::getId, toSet())));
final Map<String, Collection> collectionsMap = task.getDaoContextSnapshot().getCollectionDao().getCollectionsByIds(documentIdsByCollection.keySet()).stream().collect(Collectors.toMap(Collection::getId, coll -> coll));
final Set<String> collectionsChanged = new HashSet<>();
Map<String, Document> documentsByCorrelationId = createdDocuments.stream().collect(Collectors.toMap(doc -> doc.createIfAbsentMetaData().getString(Document.META_CORRELATION_ID), Function.identity()));
// aggregate all operations to individual documents
final Map<String, List<DocumentOperation>> changesByDocumentId = Utils.categorize(operations.stream(), change -> change.getEntity().getId());
final Set<String> unprocessedCreatedDocuments = createdDocuments.stream().map(Document::getId).collect(toSet());
createdDocuments.forEach(document -> {
final Collection collection = collectionsMap.get(document.getCollectionId());
final DataDocument newDataDecoded = constraintManager.encodeDataTypes(collection, document.getData());
auditAdapter.registerCreate(collection.getId(), ResourceType.DOCUMENT, document.getId(), task.getInitiator(), automationName, null, newDataDecoded);
});
changesByDocumentId.forEach((id, changeList) -> {
unprocessedCreatedDocuments.remove(id);
final Document document = changeList.get(0).getEntity();
final Document originalDocument = (task instanceof RuleTask) ? ((RuleTask) task).getOldDocument() : ((task instanceof FunctionTask) ? ((FunctionTask) task).getOriginalDocumentOrDefault(id, changeList.get(0).getOriginalDocument()) : changeList.get(0).getOriginalDocument());
final Collection collection = collectionsMap.get(document.getCollectionId());
final DataDocument aggregatedUpdate = new DataDocument();
changeList.forEach(change -> aggregatedUpdate.put(change.getAttrId(), change.getValue()));
final DataDocument newData = constraintManager.encodeDataTypes(collection, aggregatedUpdate);
final DataDocument oldData = originalDocument != null ? new DataDocument(originalDocument.getData()) : new DataDocument();
Set<String> attributesIdsToAdd = new HashSet<>(newData.keySet());
attributesIdsToAdd.removeAll(oldData.keySet());
if (attributesIdsToAdd.size() > 0) {
collection.getAttributes().stream().filter(attr -> attributesIdsToAdd.contains(attr.getId())).forEach(attr -> {
attr.setUsageCount(attr.getUsageCount() + 1);
collection.setLastTimeUsed(ZonedDateTime.now());
collectionsChanged.add(collection.getId());
});
}
document.setUpdatedBy(task.getInitiator().getId());
document.setUpdateDate(ZonedDateTime.now());
final DataDocument beforePatch = task.getDaoContextSnapshot().getDataDao().getData(document.getCollectionId(), document.getId());
DataDocument patchedData = task.getDaoContextSnapshot().getDataDao().patchData(document.getCollectionId(), document.getId(), newData);
Document updatedDocument = task.getDaoContextSnapshot().getDocumentDao().updateDocument(document.getId(), document);
updatedDocument.setData(patchedData);
// notify delayed actions about data change
if (collection.getPurposeType() == CollectionPurposeType.Tasks) {
final Document original;
if (originalDocument == null) {
// when triggered by an action button, let's use the document from db
original = new Document(document);
original.setData(beforePatch);
} else {
original = originalDocument;
}
purposeChangeProcessor.processChanges(new UpdateDocument(updatedDocument, original), collection);
}
var oldDataDecoded = constraintManager.decodeDataTypes(collection, beforePatch);
var patchedDataDecoded = constraintManager.decodeDataTypes(collection, patchedData);
auditAdapter.registerDataChange(updatedDocument.getCollectionId(), ResourceType.DOCUMENT, updatedDocument.getId(), task.getInitiator(), automationName, null, beforePatch, oldDataDecoded, patchedData, patchedDataDecoded);
// add patched data to new documents
boolean created = false;
if (StringUtils.isNotEmpty(document.createIfAbsentMetaData().getString(Document.META_CORRELATION_ID))) {
final Document doc = documentsByCorrelationId.get(document.getMetaData().getString(Document.META_CORRELATION_ID));
if (doc != null) {
doc.setData(patchedData);
created = true;
}
}
if (task instanceof RuleTask) {
if (created) {
taskProcessingFacade.onCreateDocument(new CreateDocument(updatedDocument));
} else {
if (task.getRecursionDepth() == 0) {
// there are now 3 versions of the document:
// 1) the document before user triggered an update - original document (null when triggered by action button)
// 2) the document with the new user entered value - before patch
// 3) the document with the value computed by the rule based on the previous two - updated document
// this rule got executed because of change from 1 to 2
// for the recursive rules, we need to trigger rules for changes between 2 and 3
final UpdateDocument updateDocumentEvent;
final Document orig = new Document(document);
orig.setData(beforePatch);
updateDocumentEvent = new UpdateDocument(updatedDocument, orig);
taskProcessingFacade.onDocumentUpdate(updateDocumentEvent, ((RuleTask) task).getRule().getName());
} else {
taskExecutor.submitTask(functionFacade.createTaskForUpdateDocument(collection, originalDocument, updatedDocument, aggregatedUpdate.keySet()));
}
}
}
patchedData = constraintManager.decodeDataTypes(collection, patchedData);
updatedDocument.setData(patchedData);
updatedDocuments.computeIfAbsent(document.getCollectionId(), key -> new ArrayList<>()).add(updatedDocument);
});
unprocessedCreatedDocuments.forEach(id -> {
createdDocuments.stream().filter(d -> d.getId().equals(id)).findFirst().ifPresent(document -> {
taskProcessingFacade.onCreateDocument(new CreateDocument(document));
});
});
changesTracker.addCollections(collectionsChanged.stream().map(collectionsMap::get).collect(toSet()));
changesTracker.addUpdatedDocuments(updatedDocuments.values().stream().flatMap(java.util.Collection::stream).collect(toSet()));
changesTracker.updateCollectionsMap(collectionsMapForCreatedDocuments);
changesTracker.updateCollectionsMap(collectionsMap);
collectionsChanged.forEach(collectionId -> task.getDaoContextSnapshot().getCollectionDao().updateCollection(collectionId, collectionsMap.get(collectionId), null, false));
return updatedDocuments.values().stream().flatMap(java.util.Collection::stream).collect(toList());
}
use of io.lumeer.engine.api.event.CreateDocument in project engine by Lumeer.
the class TaskUpdateChangeDetector method detectChanges.
@Override
public void detectChanges(final DocumentEvent documentEvent, final Collection collection) {
final CollectionPurpose purpose = collection.getPurpose();
final boolean doneState = isDoneState(documentEvent, collection);
if (!(documentEvent instanceof CreateDocument) && !(documentEvent instanceof RemoveDocument)) {
// delete previous due date and assignee events on the document
delayedActionDao.deleteScheduledActions(getResourcePath(documentEvent), Set.of(NotificationType.TASK_UPDATED));
if (!doneState) {
final Set<Assignee> observers = getObservers(documentEvent, collection);
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_UPDATED, nowPlus(), observers));
// assignee != initiator, initiator != observer, no change in due date, state, nor assignee => send task update
final String assigneeAttr = purpose.getAssigneeAttributeId();
final String dueDateAttr = purpose.getDueDateAttributeId();
final String stateAttr = purpose.getStateAttributeId();
if (!isAttributeChanged(documentEvent, assigneeAttr) && !isAttributeChanged(documentEvent, dueDateAttr) && !isAttributeChanged(documentEvent, stateAttr)) {
if (observers != null) {
// prevent double notification when assignee is also an observer
final Set<Assignee> assignees = new HashSet<>(getAssignees(documentEvent, collection));
observers.forEach(assignee -> {
assignees.remove(new Assignee(assignee.getEmail(), true));
assignees.remove(new Assignee(assignee.getEmail(), false));
});
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_UPDATED, nowPlus(), assignees));
} else {
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_UPDATED, nowPlus()));
}
}
}
}
if (documentEvent instanceof RemoveDocument) {
// create new due date events on the document
if (!doneState) {
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_REMOVED, nowPlus()));
delayedActionDao.scheduleActions(getDelayedActions(documentEvent, collection, NotificationType.TASK_REMOVED, nowPlus(), getObservers(documentEvent, collection)));
}
}
}
use of io.lumeer.engine.api.event.CreateDocument in project engine by Lumeer.
the class DocumentFacade method createDocumentsChain.
public DocumentsChain createDocumentsChain(List<Document> documents, List<LinkInstance> linkInstances) {
var collectionsMap = documents.stream().filter(document -> document.getId() == null).map(document -> checkCreateDocuments(document.getCollectionId())).collect(Collectors.toMap(Resource::getId, Function.identity()));
var linkTypesMap = linkInstances.stream().filter(linkInstance -> linkInstance.getId() == null).map(linkInstanceId -> checkCreateLinks(linkInstanceId.getLinkTypeId())).collect(Collectors.toMap(LinkType::getId, Function.identity()));
permissionsChecker.checkDocumentLimits(documents);
if (documents.isEmpty()) {
return new DocumentsChain(Collections.emptyList(), Collections.emptyList());
}
List<Document> createdDocuments = new ArrayList<>();
List<LinkInstance> createdLinks = new ArrayList<>();
String previousDocumentId = linkInstances.size() == documents.size() ? Utils.firstNotNullElement(linkInstances.get(0).getDocumentIds()) : null;
var linkInstanceIndex = 0;
for (Document document : documents) {
String currentDocumentId;
if (document.getId() != null) {
currentDocumentId = document.getId();
} else {
var collection = collectionsMap.get(document.getCollectionId());
var tuple = createDocument(collection, document);
createdDocuments.add(tuple.getSecond());
currentDocumentId = tuple.getFirst().getId();
collectionAdapter.updateCollectionMetadata(collection, tuple.getSecond().getData().keySet(), Collections.emptySet());
}
var linkInstance = linkInstances.size() > linkInstanceIndex ? linkInstances.get(linkInstanceIndex) : null;
if (previousDocumentId != null && linkInstance != null) {
linkInstance.setDocumentIds(Arrays.asList(previousDocumentId, currentDocumentId));
if (linkInstance.getId() != null) {
var updatedLinkInstance = linkInstanceDao.updateLinkInstance(linkInstance.getId(), linkInstance);
updatedLinkInstance.setData(linkInstance.getData());
createdLinks.add(updatedLinkInstance);
} else {
var linkType = linkTypesMap.get(linkInstance.getLinkTypeId());
var linkData = linkInstanceFacade.createLinkInstance(linkType, linkInstance);
createdLinks.add(linkData.getSecond());
}
linkInstanceIndex++;
}
previousDocumentId = currentDocumentId;
}
if (this.createChainEvent != null) {
this.createChainEvent.fire(new CreateDocumentsAndLinks(createdDocuments, createdLinks));
}
return new DocumentsChain(createdDocuments, createdLinks);
}
use of io.lumeer.engine.api.event.CreateDocument in project engine by Lumeer.
the class TaskProcessingFacade method onCreateDocument.
public void onCreateDocument(@Observes final CreateDocument createDocument) {
List<Task> tasks = documentCreatedTasks(new Document(createDocument.getDocument()));
processTasks(tasks.toArray(new Task[0]));
}
Aggregations