/*
* Copyright (c) 2010-2013 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.notifications.api.events;
import com.evolveum.midpoint.notifications.api.NotificationFunctions;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.LightweightIdentifier;
import com.evolveum.midpoint.task.api.LightweightIdentifierGenerator;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.jetbrains.annotations.NotNull;
import javax.xml.namespace.QName;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* @author mederly
*/
public abstract class BaseEvent implements Event {
private LightweightIdentifier id; // randomly generated event ID
private SimpleObjectRef requester; // who requested this operation (null if unknown)
/**
* If needed, we can prescribe the handler that should process this event. It is recommended only for ad-hoc situations.
* A better is to define handlers in system configuration.
*/
protected final EventHandlerType adHocHandler;
private transient NotificationFunctions notificationFunctions; // needs not be set when creating an event ... it is set in NotificationManager
// about who is this operation (null if unknown);
// - for model notifications, this is the focus, (usually a user but may be e.g. role or other kind of object)
// - for account notifications, this is the account owner,
// - for workflow notifications, this is the workflow process instance object
// - for certification notifications, this is the campaign owner or reviewer (depending on the kind of event)
private SimpleObjectRef requestee;
private String channel;
public BaseEvent(@NotNull LightweightIdentifierGenerator lightweightIdentifierGenerator) {
this(lightweightIdentifierGenerator, null);
}
public BaseEvent(@NotNull LightweightIdentifierGenerator lightweightIdentifierGenerator, EventHandlerType adHocHandler) {
id = lightweightIdentifierGenerator.generate();
this.adHocHandler = adHocHandler;
}
public LightweightIdentifier getId() {
return id;
}
@Override
public String toString() {
return "Event{" +
"id=" + id +
",requester=" + requester +
",requestee=" + requestee +
'}';
}
abstract public boolean isStatusType(EventStatusType eventStatusType);
abstract public boolean isOperationType(EventOperationType eventOperationType);
abstract public boolean isCategoryType(EventCategoryType eventCategoryType);
public boolean isAccountRelated() {
return isCategoryType(EventCategoryType.RESOURCE_OBJECT_EVENT);
}
public boolean isUserRelated() {
return false; // overriden in ModelEvent
}
public boolean isWorkItemRelated() {
return isCategoryType(EventCategoryType.WORK_ITEM_EVENT);
}
public boolean isWorkflowProcessRelated() {
return isCategoryType(EventCategoryType.WORKFLOW_PROCESS_EVENT);
}
public boolean isWorkflowRelated() {
return isCategoryType(EventCategoryType.WORKFLOW_EVENT);
}
public boolean isCertCampaignStageRelated() {
return isCategoryType(EventCategoryType.CERT_CAMPAIGN_STAGE_EVENT);
}
public boolean isAdd() {
return isOperationType(EventOperationType.ADD);
}
public boolean isModify() {
return isOperationType(EventOperationType.MODIFY);
}
public boolean isDelete() {
return isOperationType(EventOperationType.DELETE);
}
public boolean isSuccess() {
return isStatusType(EventStatusType.SUCCESS);
}
public boolean isAlsoSuccess() {
return isStatusType(EventStatusType.ALSO_SUCCESS);
}
public boolean isFailure() {
return isStatusType(EventStatusType.FAILURE);
}
public boolean isOnlyFailure() {
return isStatusType(EventStatusType.ONLY_FAILURE);
}
public boolean isInProgress() {
return isStatusType(EventStatusType.IN_PROGRESS);
}
// requester
public SimpleObjectRef getRequester() {
return requester;
}
public String getRequesterOid() {
return requester.getOid();
}
public void setRequester(SimpleObjectRef requester) {
this.requester = requester;
}
// requestee
public SimpleObjectRef getRequestee() {
return requestee;
}
public String getRequesteeOid() {
return requestee.getOid();
}
public ObjectType getRequesteeObject() {
if (requestee == null) {
return null;
}
return requestee.resolveObjectType(new OperationResult(BaseEvent.class + ".getRequesteeObject"), true);
}
public PolyStringType getRequesteeDisplayName() {
if (requestee == null) {
return null;
}
ObjectType requesteeObject = getRequesteeObject();
if (requesteeObject == null) {
return null;
}
if (requesteeObject instanceof UserType) {
return ((UserType) requesteeObject).getFullName();
} else if (requesteeObject instanceof AbstractRoleType) {
return ((AbstractRoleType) requesteeObject).getDisplayName();
} else {
return requesteeObject.getName();
}
}
public PolyStringType getRequesteeName() {
if (requestee == null) {
return null;
}
ObjectType requesteeObject = getRequesteeObject();
return requesteeObject != null ? requesteeObject.getName() : null;
}
public void setRequestee(SimpleObjectRef requestee) {
this.requestee = requestee;
}
boolean changeTypeMatchesOperationType(ChangeType changeType, EventOperationType eventOperationType) {
switch (eventOperationType) {
case ADD: return changeType == ChangeType.ADD;
case MODIFY: return changeType == ChangeType.MODIFY;
case DELETE: return changeType == ChangeType.DELETE;
default: throw new IllegalStateException("Unexpected EventOperationType: " + eventOperationType);
}
}
public void createExpressionVariables(Map<QName, Object> variables, OperationResult result) {
variables.put(SchemaConstants.C_EVENT, this);
variables.put(SchemaConstants.C_REQUESTER, requester != null ? requester.resolveObjectType(result, false) : null);
variables.put(SchemaConstants.C_REQUESTEE, requestee != null ? requestee.resolveObjectType(result, true) : null);
}
// Finding items in deltas/objects
// this is similar to delta.hasItemDelta but much, much more relaxed (we completely ignore ID path segments and we take subpaths into account)
//
// Very experimental implementation. Needs a bit of time to clean up and test adequately.
public <O extends ObjectType> boolean containsItem(ObjectDelta<O> delta, ItemPath itemPath) {
if (delta.getChangeType() == ChangeType.ADD) {
return containsItem(delta.getObjectToAdd(), itemPath);
} else if (delta.getChangeType() == ChangeType.MODIFY) {
return containsItemInModifications(delta.getModifications(), itemPath);
} else {
return false;
}
}
private boolean containsItemInModifications(Collection<? extends ItemDelta> modifications, ItemPath itemPath) {
for (ItemDelta itemDelta : modifications) {
if (containsItem(itemDelta, itemPath)) {
return true;
}
}
return false;
}
private boolean containsItem(ItemDelta itemDelta, ItemPath itemPath) {
ItemPath namesOnlyPathTested = itemPath.namedSegmentsOnly();
ItemPath namesOnlyPathInDelta = itemDelta.getPath().namedSegmentsOnly();
if (namesOnlyPathTested.isSubPathOrEquivalent(namesOnlyPathInDelta)) {
return true;
}
// however, we can add/delete whole container (containing part of the path)
// e.g. we can test for activation/administrativeStatus, and the delta is:
// ADD activation VALUE (administrativeStatus=ENABLED)
if (!namesOnlyPathInDelta.isSubPath(namesOnlyPathTested)) {
return false;
}
// for ADD values we know
// for REPLACE values we know - for values being added, but NOT for values being left behind
// for DELETE we have a problem if we are deleting "by ID" - we just don't know if the value being deleted contains the path in question or not
ItemPath remainder = namesOnlyPathTested.remainder(namesOnlyPathInDelta);
return containsItemInValues(itemDelta.getValuesToAdd(), remainder) ||
containsItemInValues(itemDelta.getValuesToReplace(), remainder) ||
containsItemInValues(itemDelta.getValuesToDelete(), remainder);
}
// remainder contains only named segments and is not empty
private boolean containsItemInValues(Collection<PrismValue> values, ItemPath remainder) {
if (values == null) {
return false;
}
for (PrismValue value : values) {
if (value instanceof PrismContainerValue) { // we do not want to look inside references nor primitive values
if (containsItem((PrismContainerValue) value, remainder)) {
return true;
}
}
}
return false;
}
public boolean containsItem(List<ObjectDelta<FocusType>> deltas, ItemPath itemPath) {
for (ObjectDelta objectDelta : deltas) {
if (containsItem(objectDelta, itemPath)) {
return true;
}
}
return false;
}
// itemPath is empty or starts with named item path segment
private boolean containsItem(PrismContainer container, ItemPath itemPath) {
if (container.size() == 0) {
return false; // there is a container, but no values
}
if (itemPath.isEmpty()) {
return true;
}
for (Object o : container.getValues()) {
if (containsItem((PrismContainerValue) o, itemPath)) {
return true;
}
}
return false;
}
// path starts with named item path segment
private boolean containsItem(PrismContainerValue prismContainerValue, ItemPath itemPath) {
QName first = ((NameItemPathSegment) itemPath.first()).getName();
Item item = prismContainerValue.findItem(first);
if (item == null) {
return false;
}
ItemPath pathTail = pathTail(itemPath);
if (item instanceof PrismContainer) {
return containsItem((PrismContainer) item, pathTail);
} else if (item instanceof PrismReference) {
return pathTail.isEmpty(); // we do not want to look inside references
} else if (item instanceof PrismProperty) {
return pathTail.isEmpty(); // ...neither inside atomic values
} else {
return false; // should not occur anyway
}
}
private ItemPath pathTail(ItemPath itemPath) {
while (!itemPath.isEmpty() && itemPath.first() instanceof IdItemPathSegment) {
itemPath = itemPath.tail();
}
return itemPath;
}
@Override
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public NotificationFunctions getNotificationFunctions() {
return notificationFunctions;
}
public void setNotificationFunctions(NotificationFunctions notificationFunctions) {
this.notificationFunctions = notificationFunctions;
}
public String getStatusAsText() {
if (isSuccess()) {
return "SUCCESS";
} else if (isOnlyFailure()) {
return "FAILURE";
} else if (isFailure()) {
return "PARTIAL FAILURE";
} else if (isInProgress()) {
return "IN PROGRESS";
} else {
return "UNKNOWN";
}
}
@Override
public EventHandlerType getAdHocHandler() {
return adHocHandler;
}
}