Skip to content

Commit

Permalink
Workflows: Timed delegation/escalation/completion and notifications n…
Browse files Browse the repository at this point in the history
…ow should work at an acceptable level.
  • Loading branch information
mederly committed Feb 9, 2017
1 parent e168567 commit afa61af
Show file tree
Hide file tree
Showing 19 changed files with 551 additions and 115 deletions.
Expand Up @@ -17,15 +17,10 @@

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;

import javax.xml.bind.JAXBElement;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

Expand Down Expand Up @@ -1178,4 +1173,16 @@ public static void assertInstanceOf(Class<?> expectedClass, Object object) {
expectedClass.isAssignableFrom(object.getClass()));
}

public static void assertDuration(String message, String durationString, long start, XMLGregorianCalendar end, Long precision) {
assertNotNull("expected duration is null", durationString);
assertNotNull("end time is null", end);
XMLGregorianCalendar startGC = XmlTypeConverter.createXMLGregorianCalendar(start);
startGC.add(XmlTypeConverter.createDuration(durationString));
long difference = Math.abs(XmlTypeConverter.toMillis(startGC) - XmlTypeConverter.toMillis(end));
long threshold = precision != null ? precision : 60000L;
if (difference > threshold) {
fail(message + ": Wrong time interval between " + new Date(start) + " and " + end + ": expected " + durationString
+ " (precision of " + threshold + "); real difference with the expected value is " + difference);
}
}
}
Expand Up @@ -41,6 +41,7 @@
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -876,6 +877,10 @@ <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query)

Collection<String> getManagersOidsExceptUser(UserType user) throws SchemaException, ObjectNotFoundException, SecurityViolationException;

Collection<String> getManagersOidsExceptUser(@NotNull Collection<ObjectReferenceType> userRefList)
throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException,
ConfigurationException;

Collection<UserType> getManagers(UserType user) throws SchemaException, ObjectNotFoundException, SecurityViolationException;

Collection<UserType> getManagersByOrgType(UserType user, String orgType) throws SchemaException, ObjectNotFoundException, SecurityViolationException;
Expand Down
Expand Up @@ -16,12 +16,12 @@

package com.evolveum.midpoint.model.api.expr;

import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import org.jetbrains.annotations.NotNull;

import javax.xml.namespace.QName;
import java.util.Collection;
Expand All @@ -35,6 +35,10 @@ public interface OrgStructFunctions {

Collection<String> getManagersOidsExceptUser(UserType user, boolean preAuthorized) throws SchemaException, ObjectNotFoundException, SecurityViolationException;

Collection<String> getManagersOidsExceptUser(@NotNull Collection<ObjectReferenceType> userRefList, boolean preAuthorized)
throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException,
ConfigurationException;

Collection<UserType> getManagers(UserType user, boolean preAuthorized) throws SchemaException, ObjectNotFoundException, SecurityViolationException;

Collection<UserType> getManagersByOrgType(UserType user, String orgType, boolean preAuthorized) throws SchemaException, ObjectNotFoundException, SecurityViolationException;
Expand Down
Expand Up @@ -65,6 +65,7 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -1119,7 +1120,14 @@ public Collection<String> getManagersOidsExceptUser(UserType user) throws Schema
return orgStructFunctions.getManagersOidsExceptUser(user, false);
}

@Override
@Override
public Collection<String> getManagersOidsExceptUser(@NotNull Collection<ObjectReferenceType> userRefList)
throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException,
ConfigurationException {
return orgStructFunctions.getManagersOidsExceptUser(userRefList, false);
}

@Override
public OrgType getOrgByName(String name) throws SchemaException, SecurityViolationException {
return orgStructFunctions.getOrgByName(name, false);
}
Expand Down
Expand Up @@ -17,11 +17,12 @@

import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.expr.OrgStructFunctions;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.PrismConstants;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.RefFilter;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
Expand All @@ -36,6 +37,7 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -85,7 +87,7 @@ public Collection<String> getManagersOids(UserType user, boolean preAuthorized)

@Override
public Collection<String> getManagersOidsExceptUser(UserType user, boolean preAuthorized) throws SchemaException, ObjectNotFoundException, SecurityViolationException {
Set<String> retval = new HashSet<String>();
Set<String> retval = new HashSet<>();
for (UserType u : getManagers(user, preAuthorized)) {
if (!u.getOid().equals(user.getOid())) {
retval.add(u.getOid());
Expand All @@ -94,6 +96,17 @@ public Collection<String> getManagersOidsExceptUser(UserType user, boolean preAu
return retval;
}

@Override
public Collection<String> getManagersOidsExceptUser(@NotNull Collection<ObjectReferenceType> userRefList, boolean preAuthorized)
throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException {
Set<String> rv = new HashSet<>();
for (ObjectReferenceType ref : userRefList) {
UserType user = getObject(UserType.class, ref.getOid(), preAuthorized);
rv.addAll(getManagersOidsExceptUser(user, preAuthorized));
}
return rv;
}

@Override
public Collection<UserType> getManagers(UserType user, boolean preAuthorized) throws SchemaException, ObjectNotFoundException, SecurityViolationException {
return getManagers(user, null, false, preAuthorized);
Expand Down
Expand Up @@ -76,34 +76,34 @@ public TriggerScannerTaskHandler() {
super(ObjectType.class, "Trigger scan", OperationConstants.TRIGGER_SCAN);
}

// task OID -> handlerUri -> OIDs; cleared on task start
// task OID -> handlerUri -> OID+TriggerID; cleared on task start
// we use plain map, as it is much easier to synchronize explicitly than to play with ConcurrentMap methods
private Map<String,Map<String,Set<String>>> processedOidsMap = new HashMap<>();
private Map<String,Map<String,Set<String>>> processedTriggersMap = new HashMap<>();

private synchronized void initProcessedOids(Task coordinatorTask) {
private synchronized void initProcessedTriggers(Task coordinatorTask) {
Validate.notNull(coordinatorTask.getOid(), "Task OID is null");
processedOidsMap.put(coordinatorTask.getOid(), new HashMap<>());
processedTriggersMap.put(coordinatorTask.getOid(), new HashMap<>());
}

// TODO fix possible (although very small) memory leak occurring when task finishes unsuccessfully
private synchronized void cleanupProcessedOids(Task coordinatorTask) {
Validate.notNull(coordinatorTask.getOid(), "Task OID is null");
processedOidsMap.remove(coordinatorTask.getOid());
processedTriggersMap.remove(coordinatorTask.getOid());
}

private synchronized boolean oidAlreadySeen(Task coordinatorTask, String handlerUri, String objectOid) {
private synchronized boolean triggerAlreadySeen(Task coordinatorTask, String handlerUri, String oidPlusTriggerId) {
Validate.notNull(coordinatorTask.getOid(), "Coordinator task OID is null");
Map<String,Set<String>> taskMap = processedOidsMap.get(coordinatorTask.getOid());
if (taskMap == null) {
throw new IllegalStateException("ProcessedOids map was not initialized for task = " + coordinatorTask);
Map<String,Set<String>> taskTriggersMap = processedTriggersMap.get(coordinatorTask.getOid());
if (taskTriggersMap == null) {
throw new IllegalStateException("ProcessedTriggers map was not initialized for task = " + coordinatorTask);
}
Set<String> processedOids = taskMap.get(handlerUri);
if (processedOids != null) {
return !processedOids.add(objectOid);
Set<String> processedTriggers = taskTriggersMap.get(handlerUri);
if (processedTriggers != null) {
return !processedTriggers.add(oidPlusTriggerId);
} else {
processedOids = new HashSet<>();
processedOids.add(objectOid);
taskMap.put(handlerUri, processedOids);
processedTriggers = new HashSet<>();
processedTriggers.add(oidPlusTriggerId);
taskTriggersMap.put(handlerUri, processedTriggers);
return false;
}
}
Expand All @@ -121,7 +121,7 @@ protected Class<? extends ObjectType> getType(Task task) {
@Override
protected ObjectQuery createQuery(AbstractScannerResultHandler<ObjectType> handler, TaskRunResult runResult, Task task, OperationResult opResult) throws SchemaException {

initProcessedOids(task);
initProcessedTriggers(task);

ObjectQuery query = new ObjectQuery();
ObjectFilter filter;
Expand Down Expand Up @@ -220,8 +220,8 @@ private void fireTrigger(TriggerType trigger, PrismObject<ObjectType> object, Ta
TriggerHandler handler = triggerHandlerRegistry.getHandler(handlerUri);
if (handler == null) {
LOGGER.warn("No registered trigger handler for URI {}", trigger);
} else if (oidAlreadySeen(coordinatorTask, handlerUri, object.getOid())) {
LOGGER.trace("Handler {} already executed for {}", trigger, ObjectTypeUtil.toShortString(object));
} else if (triggerAlreadySeen(coordinatorTask, handlerUri, object.getOid()+":"+trigger.getId())) {
LOGGER.trace("Handler {} already executed for {}:{}", trigger, ObjectTypeUtil.toShortString(object), trigger.getId());
} else {
try {
handler.handle(object, trigger, workerTask, result);
Expand Down
Expand Up @@ -141,9 +141,9 @@ public void onWorkItemAllocationChangeNewActors(WorkItemType workItem, List<Obje
checkOids(currentActors);
checkOids(newActors);
for (ObjectReferenceType newActor : newActors) {
if (!ObjectTypeUtil.containsOid(currentActors, newActor.getOid())) {
// if (!ObjectTypeUtil.containsOid(currentActors, newActor.getOid())) {
onWorkItemAllocationAdd(newActor, workItem, operationKind, initiator, source, cause, task, result);
}
// }
}
}

Expand Down
Expand Up @@ -112,7 +112,7 @@ private String getSubjectFromWorkItemEvent(WorkItemEvent event, GeneralNotifierT
if (event.getOperationKind() == null) {
throw new IllegalStateException("Missing operationKind in " + event);
}
String rv = "Work item is to be automatically " + getOperationPastTenseVerb(event.getOperationKind());
String rv = "Work item will be automatically " + getOperationPastTenseVerb(event.getOperationKind());
if (event.getTimeBefore() != null) { // should always be
rv += " in " + DurationFormatUtils.formatDurationWords(
event.getTimeBefore().getTimeInMillis(new Date()), true, true);
Expand Down Expand Up @@ -185,7 +185,13 @@ private void appendWorkItemInformation(StringBuilder sb, WorkItemEvent event, Op
sb.append("Originally allocated to: ").append(formatUserName(originalAssigneeObject, originalAssignee.getOid())).append("\n");
}
if (!workItem.getAssigneeRef().isEmpty()) {
sb.append("Allocated to: ");
sb.append("Allocated to");
if (event.getOperationKind() == WorkItemOperationKindType.DELEGATE) {
sb.append(event.isAdd() ? " (after delegation)" : " (before delegation)");
} else if (event.getOperationKind() == WorkItemOperationKindType.ESCALATE) {
sb.append(event.isAdd() ? " (after escalation)" : " (before escalation)");
}
sb.append(": ");
sb.append(workItem.getAssigneeRef().stream()
.map(ref -> formatUserName(ref, result))
.collect(Collectors.joining(", ")));
Expand Down
Expand Up @@ -171,7 +171,7 @@ public void releaseWorkItem(String workItemId, OperationResult result) throws Se
@Override
public void delegateWorkItem(String workItemId, List<ObjectReferenceType> delegates, WorkItemDelegationMethodType method,
OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException {
workItemManager.delegateWorkItem(workItemId, delegates, method, false, null, null, null, parentResult);
workItemManager.delegateWorkItem(workItemId, delegates, method, false, null, null, null, null, parentResult);
}

/*
Expand Down

0 comments on commit afa61af

Please sign in to comment.