/*
* Copyright (c) 2010-2015 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.schema.statistics;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectActionsExecutedEntryType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActionsExecutedInformationType;
import org.apache.commons.lang.StringUtils;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Pavol Mederly
*/
public class ActionsExecutedInformation {
/*
* Thread safety: Just like EnvironmentalPerformanceInformation, instances of this class may be accessed from
* more than one thread at once. Updates are invoked in the context of the thread executing the task.
* Queries are invoked either from this thread, or from some observer (task manager or GUI thread).
*/
private final ActionsExecutedInformationType startValue;
// Note: key-related fields in value objects (objectType, channel, ...) are filled-in but ignored in the following two maps
// allObjectActions: all executed actions
private Map<ActionsExecutedObjectsKey,ObjectActionsExecutedEntryType> allObjectActions = new HashMap<>();
// resultingObjectActions: "cleaned up" actions - i.e. if an object is added and then modified (in one "logical" operation),
// we count it as 1xADD
private Map<ActionsExecutedObjectsKey,ObjectActionsExecutedEntryType> resultingObjectActions = new HashMap<>();
// operations executed in the current scope: we "clean them up" when markObjectActionExecutedBoundary is called
// (indexed by object oid)
private Map<String,List<ObjectActionExecuted>> currentScopeObjectActions = new HashMap<>();
public ActionsExecutedInformation(ActionsExecutedInformationType value) {
startValue = value;
}
public ActionsExecutedInformation() {
this(null);
}
public ActionsExecutedInformationType getStartValue() {
return (ActionsExecutedInformationType) startValue;
}
public synchronized ActionsExecutedInformationType getDeltaValue() {
ActionsExecutedInformationType rv = toActionsExecutedInformationType();
return rv;
}
public synchronized ActionsExecutedInformationType getAggregatedValue() {
ActionsExecutedInformationType delta = toActionsExecutedInformationType();
ActionsExecutedInformationType rv = aggregate(startValue, delta);
return rv;
}
private ActionsExecutedInformationType aggregate(ActionsExecutedInformationType startValue, ActionsExecutedInformationType delta) {
if (startValue == null) {
return delta;
}
ActionsExecutedInformationType rv = new ActionsExecutedInformationType();
addTo(rv, startValue);
addTo(rv, delta);
return rv;
}
public static void addTo(ActionsExecutedInformationType sum, ActionsExecutedInformationType delta) {
addTo(sum.getObjectActionsEntry(), delta.getObjectActionsEntry());
addTo(sum.getResultingObjectActionsEntry(), delta.getResultingObjectActionsEntry());
}
public static void addTo(List<ObjectActionsExecutedEntryType> sumEntries, List<ObjectActionsExecutedEntryType> deltaEntries) {
for (ObjectActionsExecutedEntryType entry : deltaEntries) {
ObjectActionsExecutedEntryType matchingEntry = findEntry(sumEntries, entry);
if (matchingEntry != null) {
addToEntry(matchingEntry, entry);
} else {
sumEntries.add(entry);
}
}
}
private static void addToEntry(ObjectActionsExecutedEntryType sum, ObjectActionsExecutedEntryType delta) {
sum.setTotalSuccessCount(sum.getTotalSuccessCount() + delta.getTotalSuccessCount());
if (delta.getLastSuccessTimestamp() != null &&
(sum.getLastSuccessTimestamp() == null || delta.getLastSuccessTimestamp().compare(sum.getLastSuccessTimestamp()) == DatatypeConstants.GREATER)) {
sum.setLastSuccessObjectName(delta.getLastSuccessObjectName());
sum.setLastSuccessObjectDisplayName(delta.getLastSuccessObjectDisplayName());
sum.setLastSuccessObjectOid(delta.getLastSuccessObjectOid());
sum.setLastSuccessTimestamp(delta.getLastSuccessTimestamp());
}
sum.setTotalFailureCount(sum.getTotalFailureCount() + delta.getTotalFailureCount());
if (delta.getLastFailureTimestamp() != null &&
(sum.getLastFailureTimestamp() == null || delta.getLastFailureTimestamp().compare(sum.getLastFailureTimestamp()) == DatatypeConstants.GREATER)) {
sum.setLastFailureObjectName(delta.getLastFailureObjectName());
sum.setLastFailureObjectDisplayName(delta.getLastFailureObjectDisplayName());
sum.setLastFailureObjectOid(delta.getLastFailureObjectOid());
sum.setLastFailureTimestamp(delta.getLastFailureTimestamp());
sum.setLastFailureExceptionMessage(delta.getLastFailureExceptionMessage());
}
}
private static ObjectActionsExecutedEntryType findEntry(List<ObjectActionsExecutedEntryType> entries, ObjectActionsExecutedEntryType entry) {
for (ObjectActionsExecutedEntryType e : entries) {
if (entry.getObjectType().equals(e.getObjectType()) &&
entry.getOperation().equals(e.getOperation()) &&
StringUtils.equals(entry.getChannel(), e.getChannel())) {
return e;
}
}
return null;
}
private ActionsExecutedInformationType toActionsExecutedInformationType() {
ActionsExecutedInformationType rv = new ActionsExecutedInformationType();
toJaxb(rv);
return rv;
}
private void toJaxb(ActionsExecutedInformationType rv) {
mapToJaxb(allObjectActions, rv.getObjectActionsEntry());
mapToJaxb(resultingObjectActions, rv.getResultingObjectActionsEntry());
}
private void mapToJaxb(Map<ActionsExecutedObjectsKey, ObjectActionsExecutedEntryType> map, List<ObjectActionsExecutedEntryType> list) {
for (Map.Entry<ActionsExecutedObjectsKey,ObjectActionsExecutedEntryType> entry : map.entrySet()) {
ObjectActionsExecutedEntryType e = entry.getValue().clone();
e.setObjectType(entry.getKey().getObjectType());
e.setOperation(ChangeType.toChangeTypeType(entry.getKey().getOperation()));
e.setChannel(entry.getKey().getChannel());
list.add(e);
}
}
public synchronized void recordObjectActionExecuted(String objectName, String objectDisplayName, QName objectType, String objectOid, ChangeType changeType, String channel, Throwable exception) {
XMLGregorianCalendar now = XmlTypeConverter.createXMLGregorianCalendar(new Date());
ObjectActionExecuted action = new ObjectActionExecuted(objectName, objectDisplayName, objectType, objectOid, changeType, channel, exception, now);
addEntry(allObjectActions, action);
if (action.objectOid == null) {
action.objectOid = "dummy-" + ((int) Math.random() * 10000000); // hack for unsuccessful ADDs
}
addAction(action);
}
protected void addAction(ObjectActionExecuted action) {
List<ObjectActionExecuted> actions = currentScopeObjectActions.get(action.objectOid);
if (actions == null) {
actions = new ArrayList<>();
currentScopeObjectActions.put(action.objectOid, actions);
}
actions.add(action);
}
protected void addEntry(Map<ActionsExecutedObjectsKey,ObjectActionsExecutedEntryType> target, ObjectActionExecuted a) {
ActionsExecutedObjectsKey key = new ActionsExecutedObjectsKey(a.objectType, a.changeType, a.channel);
ObjectActionsExecutedEntryType entry = target.get(key);
if (entry == null) {
entry = new ObjectActionsExecutedEntryType();
target.put(key, entry);
}
if (a.exception == null) {
entry.setTotalSuccessCount(entry.getTotalSuccessCount() + 1);
entry.setLastSuccessObjectName(a.objectName);
entry.setLastSuccessObjectDisplayName(a.objectDisplayName);
entry.setLastSuccessObjectOid(a.objectOid);
entry.setLastSuccessTimestamp(a.timestamp);
} else {
entry.setTotalFailureCount(entry.getTotalFailureCount() + 1);
entry.setLastFailureObjectName(a.objectName);
entry.setLastFailureObjectDisplayName(a.objectDisplayName);
entry.setLastFailureObjectOid(a.objectOid);
entry.setLastFailureTimestamp(a.timestamp);
entry.setLastFailureExceptionMessage(a.exception.getMessage());
}
}
public synchronized void markObjectActionExecutedBoundary() {
for (Map.Entry<String,List<ObjectActionExecuted>> entry : currentScopeObjectActions.entrySet()) {
// Last non-modify operation determines the result
List<ObjectActionExecuted> actions = entry.getValue();
assert actions.size() > 0;
int relevant;
for (relevant = actions.size()-1; relevant>=0; relevant--) {
if (actions.get(relevant).changeType != ChangeType.MODIFY) {
break;
}
}
if (relevant < 0) {
// all are "modify" -> take any (we currently don't care whether successful or not; TODO fix this)
ObjectActionExecuted actionExecuted = actions.get(actions.size()-1);
addEntry(resultingObjectActions, actionExecuted);
} else {
addEntry(resultingObjectActions, actions.get(relevant));
}
}
currentScopeObjectActions.clear();
}
private static class ObjectActionExecuted {
String objectName;
String objectDisplayName;
QName objectType;
String objectOid;
ChangeType changeType;
String channel;
Throwable exception;
XMLGregorianCalendar timestamp;
public ObjectActionExecuted(String objectName, String objectDisplayName, QName objectType, String objectOid, ChangeType changeType, String channel, Throwable exception, XMLGregorianCalendar timestamp) {
this.objectName = objectName;
this.objectDisplayName = objectDisplayName;
this.objectType = objectType;
this.objectOid = objectOid;
this.changeType = changeType;
this.channel = channel;
this.exception = exception;
this.timestamp = timestamp;
}
}
}