diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/DeltaFormatter.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/DeltaFormatter.java new file mode 100644 index 00000000000..01b953af657 --- /dev/null +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/DeltaFormatter.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2010-2013 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.formatters; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.apache.commons.collections4.CollectionUtils; +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; + +import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.util.PrettyPrinter; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +/** + * Formats object deltas and item deltas for notification purposes. + */ +@Component +public class DeltaFormatter { + + @Autowired private ValueFormatter valueFormatter; + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; + @Autowired private PrismContext prismContext; + @Autowired protected NotificationFunctionsImpl functions; + @Autowired private LocalizationService localizationService; + + private static final Trace LOGGER = TraceManager.getTrace(DeltaFormatter.class); + + @SuppressWarnings("unused") + public String formatObjectModificationDelta(ObjectDelta objectDelta, List hiddenPaths, + boolean showOperationalAttributes) { + return formatObjectModificationDelta(objectDelta, hiddenPaths, showOperationalAttributes, null, null); + } + + // objectOld and objectNew are used for explaining changed container values, e.g. assignment[1]/tenantRef (see MID-2047) + // if null, they are ignored + public String formatObjectModificationDelta(@NotNull ObjectDelta objectDelta, List hiddenPaths, + boolean showOperationalAttributes, PrismObject objectOld, PrismObject objectNew) { + Validate.isTrue(objectDelta.isModify(), "objectDelta is not a modification delta"); + + PrismObjectDefinition objectDefinition; + if (objectNew != null && objectNew.getDefinition() != null) { + objectDefinition = objectNew.getDefinition(); + } else if (objectOld != null && objectOld.getDefinition() != null) { + objectDefinition = objectOld.getDefinition(); + } else { + objectDefinition = null; + } + + LOGGER.trace("formatObjectModificationDelta: objectDelta:\n{}\n\nhiddenPaths:\n{}", + objectDelta.debugDumpLazily(), PrettyPrinter.prettyPrintLazily(hiddenPaths)); + + StringBuilder sb = new StringBuilder(); + + List toBeDisplayed = filterAndOrderItemDeltas(objectDelta, hiddenPaths, showOperationalAttributes); + for (ItemDelta itemDelta : toBeDisplayed) { + sb.append(" - "); + sb.append(getItemDeltaLabel(itemDelta, objectDefinition)); + sb.append(":\n"); + formatItemDeltaContent(sb, itemDelta, objectOld, hiddenPaths, showOperationalAttributes); + } + + explainPaths(sb, toBeDisplayed, objectDefinition, objectOld, objectNew, hiddenPaths, showOperationalAttributes); + + return sb.toString(); + } + + private void explainPaths(StringBuilder sb, List deltas, PrismObjectDefinition objectDefinition, PrismObject objectOld, PrismObject objectNew, List hiddenPaths, boolean showOperationalAttributes) { + if (objectOld == null && objectNew == null) { + return; // no data - no point in trying + } + boolean first = true; + List alreadyExplained = new ArrayList<>(); + for (ItemDelta itemDelta : deltas) { + ItemPath pathToExplain = getPathToExplain(itemDelta); + if (pathToExplain == null || ItemPathCollectionsUtil.containsSubpathOrEquivalent(alreadyExplained, pathToExplain)) { + continue; // null or already processed + } + PrismObject source = null; + Object item = null; + if (objectNew != null) { + item = objectNew.find(pathToExplain); + source = objectNew; + } + if (item == null && objectOld != null) { + item = objectOld.find(pathToExplain); + source = objectOld; + } + if (item == null) { + LOGGER.warn("Couldn't find {} in {} nor {}, no explanation could be created.", pathToExplain, objectNew, objectOld); + continue; + } + if (first) { + sb.append("\nNotes:\n"); + first = false; + } + String label = getItemPathLabel(pathToExplain, itemDelta.getDefinition(), objectDefinition); + // the item should be a PrismContainerValue + if (item instanceof PrismContainerValue) { + sb.append(" - ").append(label).append(":\n"); + valueFormatter.formatContainerValue(sb, " ", (PrismContainerValue) item, false, hiddenPaths, showOperationalAttributes); + } else { + LOGGER.warn("{} in {} was expected to be a PrismContainerValue; it is {} instead", pathToExplain, source, item.getClass()); + if (item instanceof PrismContainer) { + valueFormatter.formatPrismContainer(sb, " ", (PrismContainer) item, false, hiddenPaths, showOperationalAttributes); + } else if (item instanceof PrismReference) { + valueFormatter.formatPrismReference(sb, " ", (PrismReference) item, false); + } else if (item instanceof PrismProperty) { + valueFormatter.formatPrismProperty(sb, " ", (PrismProperty) item); + } else { + sb.append("Unexpected item: ").append(item).append("\n"); + } + } + alreadyExplained.add(pathToExplain); + } + } + + private void formatItemDeltaContent(StringBuilder sb, ItemDelta itemDelta, PrismObject objectOld, + List hiddenPaths, boolean showOperationalAttributes) { + formatItemDeltaValues(sb, "ADD", itemDelta.getValuesToAdd(), false, itemDelta.getPath(), objectOld, hiddenPaths, showOperationalAttributes); + formatItemDeltaValues(sb, "DELETE", itemDelta.getValuesToDelete(), true, itemDelta.getPath(), objectOld, hiddenPaths, showOperationalAttributes); + formatItemDeltaValues(sb, "REPLACE", itemDelta.getValuesToReplace(), false, itemDelta.getPath(), objectOld, hiddenPaths, showOperationalAttributes); + } + + private void formatItemDeltaValues(StringBuilder sb, String type, Collection values, + boolean isDelete, ItemPath path, PrismObject objectOld, + List hiddenPaths, boolean showOperationalAttributes) { + if (values != null) { + for (PrismValue prismValue : values) { + sb.append(" - ").append(type).append(": "); + String prefix = " "; + if (isDelete && prismValue instanceof PrismContainerValue) { + prismValue = fixEmptyContainerValue((PrismContainerValue) prismValue, path, objectOld); + } + valueFormatter.formatPrismValue(sb, prefix, prismValue, isDelete, hiddenPaths, showOperationalAttributes); + if (!(prismValue instanceof PrismContainerValue)) { // container values already end with newline + sb.append("\n"); + } + } + } + } + + private PrismValue fixEmptyContainerValue(PrismContainerValue pcv, ItemPath path, PrismObject objectOld) { + if (pcv.getId() == null || CollectionUtils.isNotEmpty(pcv.getItems())) { + return pcv; + } + PrismContainer oldContainer = objectOld.findContainer(path); + if (oldContainer == null) { + return pcv; + } + PrismContainerValue oldValue = oldContainer.getValue(pcv.getId()); + return oldValue != null ? oldValue : pcv; + } + + // we call this on filtered list of item deltas - all of they have definition set + private String getItemDeltaLabel(ItemDelta itemDelta, PrismObjectDefinition objectDefinition) { + return getItemPathLabel(itemDelta.getPath(), itemDelta.getDefinition(), objectDefinition); + } + + private String getItemPathLabel(ItemPath path, Definition deltaDefinition, PrismObjectDefinition objectDefinition) { + + int lastNameIndex = path.lastNameIndex(); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < path.size(); i++) { + Object segment = path.getSegment(i); + if (ItemPath.isName(segment)) { + if (sb.length() > 0) { + sb.append("/"); + } + Definition itemDefinition; + if (objectDefinition == null) { + if (i == lastNameIndex) { // definition for last segment is the definition taken from delta + itemDefinition = deltaDefinition; // this may be null but we don't care + } else { + itemDefinition = null; // definitions for previous segments are unknown + } + } else { + // todo we could make this iterative (resolving definitions while walking down the path); but this is definitely simpler to implement and debug :) + itemDefinition = objectDefinition.findItemDefinition(path.allUpToIncluding(i)); + } + if (itemDefinition != null && itemDefinition.getDisplayName() != null) { + sb.append(resolve(itemDefinition.getDisplayName())); + } else { + sb.append(ItemPath.toName(segment).getLocalPart()); + } + } else if (ItemPath.isId(segment)) { + sb.append("[").append(ItemPath.toId(segment)).append("]"); + } + } + return sb.toString(); + } + + private String resolve(String key) { + if (key != null) { + return localizationService.translate(key, null, Locale.getDefault(), key); + } else { + return null; + } + } + + // we call this on filtered list of item deltas - all of they have definition set + private ItemPath getPathToExplain(ItemDelta itemDelta) { + ItemPath path = itemDelta.getPath(); + + for (int i = 0; i < path.size(); i++) { + Object segment = path.getSegment(i); + if (ItemPath.isId(segment)) { + if (i < path.size()-1 || itemDelta.isDelete()) { + return path.allUpToIncluding(i); + } else { + // this means that the path ends with [id] segment *and* the value(s) are + // only added and deleted, i.e. they are shown in the delta anyway + // (actually it is questionable whether path in delta can end with [id] segment, + // but we test for this case just to be sure) + return null; + } + } + } + return null; + } + + private List filterAndOrderItemDeltas(ObjectDelta objectDelta, List hiddenPaths, boolean showOperationalAttributes) { + List toBeDisplayed = new ArrayList<>(objectDelta.getModifications().size()); + List noDefinition = new ArrayList<>(); + for (ItemDelta itemDelta: objectDelta.getModifications()) { + if (itemDelta.getDefinition() != null) { + if ((showOperationalAttributes || !itemDelta.getDefinition().isOperational()) && !NotificationFunctionsImpl + .isAmongHiddenPaths(itemDelta.getPath(), hiddenPaths)) { + toBeDisplayed.add(itemDelta); + } + } else { + noDefinition.add(itemDelta.getElementName()); + } + } + if (!noDefinition.isEmpty()) { + LOGGER.error("ItemDeltas for {} without definition - WILL NOT BE INCLUDED IN NOTIFICATION. Containing object delta:\n{}", + noDefinition, objectDelta.debugDump()); + } + toBeDisplayed.sort((delta1, delta2) -> { + Integer order1 = delta1.getDefinition().getDisplayOrder(); + Integer order2 = delta2.getDefinition().getDisplayOrder(); + if (order1 != null && order2 != null) { + return order1 - order2; + } else if (order1 == null && order2 == null) { + return 0; + } else if (order1 == null) { + return 1; + } else { + return -1; + } + }); + return toBeDisplayed; + } +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java index 18169769b95..fb84d2fb10a 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java @@ -7,547 +7,56 @@ package com.evolveum.midpoint.notifications.impl.formatters; -import com.evolveum.midpoint.common.LocalizationService; +import java.util.List; + import com.evolveum.midpoint.notifications.api.events.SimpleObjectRef; -import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.path.*; -import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ValueDisplayUtil; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.PrettyPrinter; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import org.apache.commons.collections4.CollectionUtils; -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; -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.Objectable; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import java.util.*; +import javax.xml.datatype.XMLGregorianCalendar; import static com.evolveum.midpoint.prism.polystring.PolyString.getOrig; /** * Prepares text output for notification purposes. + * + * This is only a facade for value and delta formatters. + * It is probably used in various notification-related expressions so we'll keep it for some time. */ @Component public class TextFormatter { - @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; - @Autowired private PrismContext prismContext; - @Autowired protected NotificationFunctionsImpl functions; - @Autowired private LocalizationService localizationService; - - private static final Trace LOGGER = TraceManager.getTrace(TextFormatter.class); - - @SuppressWarnings("unused") - public String formatObjectModificationDelta(ObjectDelta objectDelta, List hiddenPaths, - boolean showOperationalAttributes) { - return formatObjectModificationDelta(objectDelta, hiddenPaths, showOperationalAttributes, null, null); - } - - // objectOld and objectNew are used for explaining changed container values, e.g. assignment[1]/tenantRef (see MID-2047) - // if null, they are ignored - public String formatObjectModificationDelta(@NotNull ObjectDelta objectDelta, List hiddenPaths, - boolean showOperationalAttributes, PrismObject objectOld, PrismObject objectNew) { - Validate.isTrue(objectDelta.isModify(), "objectDelta is not a modification delta"); - - PrismObjectDefinition objectDefinition; - if (objectNew != null && objectNew.getDefinition() != null) { - objectDefinition = objectNew.getDefinition(); - } else if (objectOld != null && objectOld.getDefinition() != null) { - objectDefinition = objectOld.getDefinition(); - } else { - objectDefinition = null; - } - - LOGGER.trace("formatObjectModificationDelta: objectDelta:\n{}\n\nhiddenPaths:\n{}", - objectDelta.debugDumpLazily(), PrettyPrinter.prettyPrintLazily(hiddenPaths)); - - StringBuilder sb = new StringBuilder(); - - List toBeDisplayed = filterAndOrderItemDeltas(objectDelta, hiddenPaths, showOperationalAttributes); - for (ItemDelta itemDelta : toBeDisplayed) { - sb.append(" - "); - sb.append(getItemDeltaLabel(itemDelta, objectDefinition)); - sb.append(":\n"); - formatItemDeltaContent(sb, itemDelta, objectOld, hiddenPaths, showOperationalAttributes); - } - - explainPaths(sb, toBeDisplayed, objectDefinition, objectOld, objectNew, hiddenPaths, showOperationalAttributes); - - return sb.toString(); - } + @Autowired ValueFormatter valueFormatter; + @Autowired DeltaFormatter deltaFormatter; - private void explainPaths(StringBuilder sb, List deltas, PrismObjectDefinition objectDefinition, PrismObject objectOld, PrismObject objectNew, List hiddenPaths, boolean showOperationalAttributes) { - if (objectOld == null && objectNew == null) { - return; // no data - no point in trying - } - boolean first = true; - List alreadyExplained = new ArrayList<>(); - for (ItemDelta itemDelta : deltas) { - ItemPath pathToExplain = getPathToExplain(itemDelta); - if (pathToExplain == null || ItemPathCollectionsUtil.containsSubpathOrEquivalent(alreadyExplained, pathToExplain)) { - continue; // null or already processed - } - PrismObject source = null; - Object item = null; - if (objectNew != null) { - item = objectNew.find(pathToExplain); - source = objectNew; - } - if (item == null && objectOld != null) { - item = objectOld.find(pathToExplain); - source = objectOld; - } - if (item == null) { - LOGGER.warn("Couldn't find {} in {} nor {}, no explanation could be created.", pathToExplain, objectNew, objectOld); - continue; - } - if (first) { - sb.append("\nNotes:\n"); - first = false; - } - String label = getItemPathLabel(pathToExplain, itemDelta.getDefinition(), objectDefinition); - // the item should be a PrismContainerValue - if (item instanceof PrismContainerValue) { - sb.append(" - ").append(label).append(":\n"); - formatContainerValue(sb, " ", (PrismContainerValue) item, false, hiddenPaths, showOperationalAttributes); - } else { - LOGGER.warn("{} in {} was expected to be a PrismContainerValue; it is {} instead", pathToExplain, source, item.getClass()); - if (item instanceof PrismContainer) { - formatPrismContainer(sb, " ", (PrismContainer) item, false, hiddenPaths, showOperationalAttributes); - } else if (item instanceof PrismReference) { - formatPrismReference(sb, " ", (PrismReference) item, false); - } else if (item instanceof PrismProperty) { - formatPrismProperty(sb, " ", (PrismProperty) item); - } else { - sb.append("Unexpected item: ").append(item).append("\n"); - } - } - alreadyExplained.add(pathToExplain); - } - } - - private void formatItemDeltaContent(StringBuilder sb, ItemDelta itemDelta, PrismObject objectOld, - List hiddenPaths, boolean showOperationalAttributes) { - formatItemDeltaValues(sb, "ADD", itemDelta.getValuesToAdd(), false, itemDelta.getPath(), objectOld, hiddenPaths, showOperationalAttributes); - formatItemDeltaValues(sb, "DELETE", itemDelta.getValuesToDelete(), true, itemDelta.getPath(), objectOld, hiddenPaths, showOperationalAttributes); - formatItemDeltaValues(sb, "REPLACE", itemDelta.getValuesToReplace(), false, itemDelta.getPath(), objectOld, hiddenPaths, showOperationalAttributes); - } - - private void formatItemDeltaValues(StringBuilder sb, String type, Collection values, - boolean isDelete, ItemPath path, PrismObject objectOld, - List hiddenPaths, boolean showOperationalAttributes) { - if (values != null) { - for (PrismValue prismValue : values) { - sb.append(" - ").append(type).append(": "); - String prefix = " "; - if (isDelete && prismValue instanceof PrismContainerValue) { - prismValue = fixEmptyContainerValue((PrismContainerValue) prismValue, path, objectOld); - } - formatPrismValue(sb, prefix, prismValue, isDelete, hiddenPaths, showOperationalAttributes); - if (!(prismValue instanceof PrismContainerValue)) { // container values already end with newline - sb.append("\n"); - } - } - } - } - - private PrismValue fixEmptyContainerValue(PrismContainerValue pcv, ItemPath path, PrismObject objectOld) { - if (pcv.getId() == null || CollectionUtils.isNotEmpty(pcv.getItems())) { - return pcv; - } - PrismContainer oldContainer = objectOld.findContainer(path); - if (oldContainer == null) { - return pcv; - } - PrismContainerValue oldValue = oldContainer.getValue(pcv.getId()); - return oldValue != null ? oldValue : pcv; - } - - // todo - should each hiddenAttribute be prefixed with something like F_ATTRIBUTE? Currently it should not be. public String formatAccountAttributes(ShadowType shadowType, List hiddenAttributes, boolean showOperationalAttributes) { - Validate.notNull(shadowType, "shadowType is null"); - - StringBuilder retval = new StringBuilder(); - if (shadowType.getAttributes() != null) { - formatContainerValue(retval, "", shadowType.getAttributes().asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); - } - if (shadowType.getCredentials() != null) { - formatContainerValue(retval, "", shadowType.getCredentials().asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); - } - if (shadowType.getActivation() != null) { - formatContainerValue(retval, "", shadowType.getActivation().asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); - } - if (shadowType.getAssociation() != null) { - boolean first = true; - for (ShadowAssociationType shadowAssociationType : shadowType.getAssociation()) { - if (first) { - first = false; - retval.append("\n"); - } - retval.append("Association:\n"); - formatContainerValue(retval, " ", shadowAssociationType.asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); - retval.append("\n"); - } - } - - return retval.toString(); + return valueFormatter.formatAccountAttributes(shadowType, hiddenAttributes, showOperationalAttributes); } public String formatObject(PrismObject object, List hiddenPaths, boolean showOperationalAttributes) { - - Validate.notNull(object, "object is null"); - - StringBuilder retval = new StringBuilder(); - formatContainerValue(retval, "", object.getValue(), false, hiddenPaths, showOperationalAttributes); - return retval.toString(); - } - - private void formatPrismValue(StringBuilder sb, String prefix, PrismValue prismValue, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { - if (prismValue instanceof PrismPropertyValue) { - sb.append(ValueDisplayUtil.toStringValue((PrismPropertyValue) prismValue)); - } else if (prismValue instanceof PrismReferenceValue) { - sb.append(formatReferenceValue((PrismReferenceValue) prismValue, mightBeRemoved)); - } else if (prismValue instanceof PrismContainerValue) { - sb.append("\n"); - formatContainerValue(sb, prefix, (PrismContainerValue) prismValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); - } else { - sb.append("Unexpected PrismValue type: "); - sb.append(prismValue); - LOGGER.error("Unexpected PrismValue type: " + prismValue.getClass() + ": " + prismValue); - } - } - - private void formatContainerValue(StringBuilder sb, String prefix, PrismContainerValue containerValue, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { -// sb.append("Container of type " + containerValue.getParent().getDefinition().getTypeName()); -// sb.append("\n"); - - List toBeDisplayed = filterAndOrderItems(containerValue.getItems(), hiddenPaths, showOperationalAttributes); - - for (Item item : toBeDisplayed) { - if (item instanceof PrismProperty) { - formatPrismProperty(sb, prefix, item); - } else if (item instanceof PrismReference) { - formatPrismReference(sb, prefix, item, mightBeRemoved); - } else if (item instanceof PrismContainer) { - formatPrismContainer(sb, prefix, item, mightBeRemoved, hiddenPaths, showOperationalAttributes); - } else { - sb.append("Unexpected Item type: "); - sb.append(item); - sb.append("\n"); - LOGGER.error("Unexpected Item type: " + item.getClass() + ": " + item); - } - } + return valueFormatter.formatObject(object, hiddenPaths, showOperationalAttributes); } - private void formatPrismContainer(StringBuilder sb, String prefix, Item item, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { - for (PrismContainerValue subContainerValue : ((PrismContainer) item).getValues()) { - String prefixSubContainer = prefix + " "; - StringBuilder valueSb = new StringBuilder(); - formatContainerValue(valueSb, prefixSubContainer, subContainerValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); - if (valueSb.length() > 0) { - sb.append(prefix); - sb.append(" - "); - sb.append(getItemLabel(item)); - if (subContainerValue.getId() != null) { - sb.append(" #").append(subContainerValue.getId()); - } - sb.append(":\n"); - sb.append(valueSb.toString()); - } - } - } - - private void formatPrismReference(StringBuilder sb, String prefix, Item item, boolean mightBeRemoved) { - sb.append(prefix); - sb.append(" - "); - sb.append(getItemLabel(item)); - sb.append(": "); - if (item.size() > 1) { - for (PrismReferenceValue referenceValue : ((PrismReference) item).getValues()) { - sb.append("\n"); - sb.append(prefix).append(" - "); - sb.append(formatReferenceValue(referenceValue, mightBeRemoved)); - } - } else if (item.size() == 1) { - sb.append(formatReferenceValue(((PrismReference) item).getAnyValue(), mightBeRemoved)); - } - sb.append("\n"); - } - - private void formatPrismProperty(StringBuilder sb, String prefix, Item item) { - sb.append(prefix); - sb.append(" - "); - sb.append(getItemLabel(item)); - sb.append(": "); - if (item.size() > 1) { - for (PrismPropertyValue propertyValue : ((PrismProperty) item).getValues()) { - sb.append("\n"); - sb.append(prefix).append(" - "); - sb.append(ValueDisplayUtil.toStringValue(propertyValue)); - } - } else if (item.size() == 1) { - sb.append(ValueDisplayUtil.toStringValue(((PrismProperty) item).getAnyValue())); - } - sb.append("\n"); - } - - private String formatReferenceValue(PrismReferenceValue value, boolean mightBeRemoved) { - - OperationResult result = new OperationResult("dummy"); - - PrismObject object = value.getObject(); - - if (object == null) { - object = getPrismObject(value.getOid(), mightBeRemoved, result); - } - - String qualifier = ""; - if (object != null && object.asObjectable() instanceof ShadowType) { - ShadowType shadowType = (ShadowType) object.asObjectable(); - ObjectReferenceType resourceRef = shadowType.getResourceRef(); - PrismObject resource = resourceRef.asReferenceValue().getObject(); - ResourceType resourceType = null; - if (resource == null) { - resource = getPrismObject(resourceRef.getOid(), false, result); - if (resource != null) { - resourceType = (ResourceType) resource.asObjectable(); - } - } else { - resourceType = resource.asObjectable(); - } - if (resourceType != null) { - qualifier = " on " + resourceType.getName(); - } else { - qualifier = " on resource " + shadowType.getResourceRef().getOid(); - } - } - - String referredObjectIdentification; - if (object != null) { - referredObjectIdentification = PolyString.getOrig(object.asObjectable().getName()) + - " (" + object.toDebugType() + ")" + - qualifier; - } else { - String nameOrOid = value.getTargetName() != null ? value.getTargetName().getOrig() : value.getOid(); - if (mightBeRemoved) { - referredObjectIdentification = "(cannot display the actual name of " + localPart(value.getTargetType()) + ":" + nameOrOid + ", as it might be already removed)"; - } else { - referredObjectIdentification = localPart(value.getTargetType()) + ":" + nameOrOid; - } - } - - return value.getRelation() != null ? - referredObjectIdentification + " [" + value.getRelation().getLocalPart() + "]" - : referredObjectIdentification; - } - - private PrismObject getPrismObject(String oid, boolean mightBeRemoved, OperationResult result) { - try { - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); - return (PrismObject) cacheRepositoryService.getObject(ObjectType.class, oid, options, result); - } catch (ObjectNotFoundException e) { - if (!mightBeRemoved) { - LoggingUtils.logException(LOGGER, "Couldn't resolve reference when displaying object name within a notification (it might be already removed)", e); - } else { - // ok, accepted - } - } catch (SchemaException e) { - LoggingUtils.logException(LOGGER, "Couldn't resolve reference when displaying object name within a notification", e); - } - return null; - } - - private String localPart(QName qname) { - return qname == null ? null : qname.getLocalPart(); - } - - // we call this on filtered list of item deltas - all of they have definition set - private String getItemDeltaLabel(ItemDelta itemDelta, PrismObjectDefinition objectDefinition) { - return getItemPathLabel(itemDelta.getPath(), itemDelta.getDefinition(), objectDefinition); - } - - private String getItemPathLabel(ItemPath path, Definition deltaDefinition, PrismObjectDefinition objectDefinition) { - - int lastNameIndex = path.lastNameIndex(); - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < path.size(); i++) { - Object segment = path.getSegment(i); - if (ItemPath.isName(segment)) { - if (sb.length() > 0) { - sb.append("/"); - } - Definition itemDefinition; - if (objectDefinition == null) { - if (i == lastNameIndex) { // definition for last segment is the definition taken from delta - itemDefinition = deltaDefinition; // this may be null but we don't care - } else { - itemDefinition = null; // definitions for previous segments are unknown - } - } else { - // todo we could make this iterative (resolving definitions while walking down the path); but this is definitely simpler to implement and debug :) - itemDefinition = objectDefinition.findItemDefinition(path.allUpToIncluding(i)); - } - if (itemDefinition != null && itemDefinition.getDisplayName() != null) { - sb.append(resolve(itemDefinition.getDisplayName())); - } else { - sb.append(ItemPath.toName(segment).getLocalPart()); - } - } else if (ItemPath.isId(segment)) { - sb.append("[").append(ItemPath.toId(segment)).append("]"); - } - } - return sb.toString(); - } - - private String resolve(String key) { - if (key != null) { - return localizationService.translate(key, null, Locale.getDefault(), key); - } else { - return null; - } - } - - // we call this on filtered list of item deltas - all of they have definition set - private ItemPath getPathToExplain(ItemDelta itemDelta) { - ItemPath path = itemDelta.getPath(); - - for (int i = 0; i < path.size(); i++) { - Object segment = path.getSegment(i); - if (ItemPath.isId(segment)) { - if (i < path.size()-1 || itemDelta.isDelete()) { - return path.allUpToIncluding(i); - } else { - // this means that the path ends with [id] segment *and* the value(s) are - // only added and deleted, i.e. they are shown in the delta anyway - // (actually it is questionable whether path in delta can end with [id] segment, - // but we test for this case just to be sure) - return null; - } - } - } - return null; - } - - private List filterAndOrderItemDeltas(ObjectDelta objectDelta, List hiddenPaths, boolean showOperationalAttributes) { - List toBeDisplayed = new ArrayList<>(objectDelta.getModifications().size()); - List noDefinition = new ArrayList<>(); - for (ItemDelta itemDelta: objectDelta.getModifications()) { - if (itemDelta.getDefinition() != null) { - if ((showOperationalAttributes || !itemDelta.getDefinition().isOperational()) && !NotificationFunctionsImpl - .isAmongHiddenPaths(itemDelta.getPath(), hiddenPaths)) { - toBeDisplayed.add(itemDelta); - } - } else { - noDefinition.add(itemDelta.getElementName()); - } - } - if (!noDefinition.isEmpty()) { - LOGGER.error("ItemDeltas for {} without definition - WILL NOT BE INCLUDED IN NOTIFICATION. Containing object delta:\n{}", - noDefinition, objectDelta.debugDump()); - } - toBeDisplayed.sort((delta1, delta2) -> { - Integer order1 = delta1.getDefinition().getDisplayOrder(); - Integer order2 = delta2.getDefinition().getDisplayOrder(); - if (order1 != null && order2 != null) { - return order1 - order2; - } else if (order1 == null && order2 == null) { - return 0; - } else if (order1 == null) { - return 1; - } else { - return -1; - } - }); - return toBeDisplayed; - } - - // we call this on filtered list of items - all of them have definition set - private String getItemLabel(Item item) { - return item.getDefinition().getDisplayName() != null ? - resolve(item.getDefinition().getDisplayName()) : item.getElementName().getLocalPart(); - } - - private List filterAndOrderItems(Collection items, List hiddenPaths, boolean showOperationalAttributes) { - if (items == null) { - return new ArrayList<>(); - } - List toBeDisplayed = new ArrayList<>(items.size()); - List noDefinition = new ArrayList<>(); - for (Item item : items) { - if (item.getDefinition() != null) { - boolean isHidden = NotificationFunctionsImpl.isAmongHiddenPaths(item.getPath(), hiddenPaths); - if (!isHidden && (showOperationalAttributes || !item.getDefinition().isOperational()) && !item.isEmpty()) { - toBeDisplayed.add(item); - } - } else { - noDefinition.add(item.getElementName()); - } - } - if (!noDefinition.isEmpty()) { - LOGGER.error("Items {} without definition - THEY WILL NOT BE INCLUDED IN NOTIFICATION.\nAll items:\n{}", - noDefinition, DebugUtil.debugDump(items)); - } - toBeDisplayed.sort((item1, item2) -> { - Integer order1 = item1.getDefinition().getDisplayOrder(); - Integer order2 = item2.getDefinition().getDisplayOrder(); - if (order1 != null && order2 != null) { - return order1 - order2; - } else if (order1 == null && order2 == null) { - return 0; - } else if (order1 == null) { - return 1; - } else { - return -1; - } - }); - return toBeDisplayed; - } - - public String formatUserName(SimpleObjectRef ref, OperationResult result) { - return formatUserName((UserType) ref.resolveObjectType(result, true), ref.getOid()); - } - - public String formatUserName(ObjectReferenceType ref, OperationResult result) { - UserType user = (UserType) functions.getObjectType(ref, true, result); - return formatUserName(user, ref.getOid()); - } - - public String formatUserName(UserType user, String oid) { - if (user == null || (user.getName() == null && user.getFullName() == null)) { - return oid; - } - if (user.getFullName() != null) { - return getOrig(user.getFullName()) + " (" + getOrig(user.getName()) + ")"; - } else { - return getOrig(user.getName()); - } + @SuppressWarnings("unused") + public String formatObjectModificationDelta(ObjectDelta objectDelta, List hiddenPaths, + boolean showOperationalAttributes) { + return deltaFormatter.formatObjectModificationDelta(objectDelta, hiddenPaths, showOperationalAttributes, null, null); } - // TODO implement seriously - public String formatDateTime(XMLGregorianCalendar timestamp) { - //DateFormatUtils.format(timestamp.toGregorianCalendar(), DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern()); - return String.valueOf(XmlTypeConverter.toDate(timestamp)); + public String formatObjectModificationDelta(@NotNull ObjectDelta objectDelta, List hiddenPaths, + boolean showOperationalAttributes, PrismObject objectOld, PrismObject objectNew) { + return deltaFormatter.formatObjectModificationDelta(objectDelta, hiddenPaths, showOperationalAttributes, objectOld, objectNew); } - } diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/ValueFormatter.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/ValueFormatter.java new file mode 100644 index 00000000000..38b75beecbe --- /dev/null +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/ValueFormatter.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2010-2013 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.formatters; + +import static com.evolveum.midpoint.prism.polystring.PolyString.getOrig; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import org.apache.commons.collections4.CollectionUtils; +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; + +import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.notifications.api.events.SimpleObjectRef; +import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ValueDisplayUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.PrettyPrinter; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * Formats prism items and their values for notification purposes. + */ +@Component +public class ValueFormatter { + + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; + @Autowired private PrismContext prismContext; + @Autowired protected NotificationFunctionsImpl functions; + @Autowired private LocalizationService localizationService; + + private static final Trace LOGGER = TraceManager.getTrace(ValueFormatter.class); + + // todo - should each hiddenAttribute be prefixed with something like F_ATTRIBUTE? Currently it should not be. + public String formatAccountAttributes(ShadowType shadowType, List hiddenAttributes, boolean showOperationalAttributes) { + Validate.notNull(shadowType, "shadowType is null"); + + StringBuilder retval = new StringBuilder(); + if (shadowType.getAttributes() != null) { + formatContainerValue(retval, "", shadowType.getAttributes().asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); + } + if (shadowType.getCredentials() != null) { + formatContainerValue(retval, "", shadowType.getCredentials().asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); + } + if (shadowType.getActivation() != null) { + formatContainerValue(retval, "", shadowType.getActivation().asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); + } + if (shadowType.getAssociation() != null) { + boolean first = true; + for (ShadowAssociationType shadowAssociationType : shadowType.getAssociation()) { + if (first) { + first = false; + retval.append("\n"); + } + retval.append("Association:\n"); + formatContainerValue(retval, " ", shadowAssociationType.asPrismContainerValue(), false, hiddenAttributes, showOperationalAttributes); + retval.append("\n"); + } + } + + return retval.toString(); + } + + public String formatObject(PrismObject object, List hiddenPaths, boolean showOperationalAttributes) { + + Validate.notNull(object, "object is null"); + + StringBuilder retval = new StringBuilder(); + formatContainerValue(retval, "", object.getValue(), false, hiddenPaths, showOperationalAttributes); + return retval.toString(); + } + + void formatPrismValue(StringBuilder sb, String prefix, PrismValue prismValue, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { + if (prismValue instanceof PrismPropertyValue) { + sb.append(ValueDisplayUtil.toStringValue((PrismPropertyValue) prismValue)); + } else if (prismValue instanceof PrismReferenceValue) { + sb.append(formatReferenceValue((PrismReferenceValue) prismValue, mightBeRemoved)); + } else if (prismValue instanceof PrismContainerValue) { + sb.append("\n"); + formatContainerValue(sb, prefix, (PrismContainerValue) prismValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); + } else { + sb.append("Unexpected PrismValue type: "); + sb.append(prismValue); + LOGGER.error("Unexpected PrismValue type: " + prismValue.getClass() + ": " + prismValue); + } + } + + void formatContainerValue(StringBuilder sb, String prefix, PrismContainerValue containerValue, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { +// sb.append("Container of type " + containerValue.getParent().getDefinition().getTypeName()); +// sb.append("\n"); + + List toBeDisplayed = filterAndOrderItems(containerValue.getItems(), hiddenPaths, showOperationalAttributes); + + for (Item item : toBeDisplayed) { + if (item instanceof PrismProperty) { + formatPrismProperty(sb, prefix, item); + } else if (item instanceof PrismReference) { + formatPrismReference(sb, prefix, item, mightBeRemoved); + } else if (item instanceof PrismContainer) { + formatPrismContainer(sb, prefix, item, mightBeRemoved, hiddenPaths, showOperationalAttributes); + } else { + sb.append("Unexpected Item type: "); + sb.append(item); + sb.append("\n"); + LOGGER.error("Unexpected Item type: " + item.getClass() + ": " + item); + } + } + } + + void formatPrismContainer(StringBuilder sb, String prefix, Item item, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { + for (PrismContainerValue subContainerValue : ((PrismContainer) item).getValues()) { + String prefixSubContainer = prefix + " "; + StringBuilder valueSb = new StringBuilder(); + formatContainerValue(valueSb, prefixSubContainer, subContainerValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); + if (valueSb.length() > 0) { + sb.append(prefix); + sb.append(" - "); + sb.append(getItemLabel(item)); + if (subContainerValue.getId() != null) { + sb.append(" #").append(subContainerValue.getId()); + } + sb.append(":\n"); + sb.append(valueSb.toString()); + } + } + } + + void formatPrismReference(StringBuilder sb, String prefix, Item item, boolean mightBeRemoved) { + sb.append(prefix); + sb.append(" - "); + sb.append(getItemLabel(item)); + sb.append(": "); + if (item.size() > 1) { + for (PrismReferenceValue referenceValue : ((PrismReference) item).getValues()) { + sb.append("\n"); + sb.append(prefix).append(" - "); + sb.append(formatReferenceValue(referenceValue, mightBeRemoved)); + } + } else if (item.size() == 1) { + sb.append(formatReferenceValue(((PrismReference) item).getAnyValue(), mightBeRemoved)); + } + sb.append("\n"); + } + + void formatPrismProperty(StringBuilder sb, String prefix, Item item) { + sb.append(prefix); + sb.append(" - "); + sb.append(getItemLabel(item)); + sb.append(": "); + if (item.size() > 1) { + for (PrismPropertyValue propertyValue : ((PrismProperty) item).getValues()) { + sb.append("\n"); + sb.append(prefix).append(" - "); + sb.append(ValueDisplayUtil.toStringValue(propertyValue)); + } + } else if (item.size() == 1) { + sb.append(ValueDisplayUtil.toStringValue(((PrismProperty) item).getAnyValue())); + } + sb.append("\n"); + } + + private String formatReferenceValue(PrismReferenceValue value, boolean mightBeRemoved) { + + OperationResult result = new OperationResult("dummy"); + + PrismObject object = value.getObject(); + + if (object == null) { + object = getPrismObject(value.getOid(), mightBeRemoved, result); + } + + String qualifier = ""; + if (object != null && object.asObjectable() instanceof ShadowType) { + ShadowType shadowType = (ShadowType) object.asObjectable(); + ObjectReferenceType resourceRef = shadowType.getResourceRef(); + PrismObject resource = resourceRef.asReferenceValue().getObject(); + ResourceType resourceType = null; + if (resource == null) { + resource = getPrismObject(resourceRef.getOid(), false, result); + if (resource != null) { + resourceType = (ResourceType) resource.asObjectable(); + } + } else { + resourceType = resource.asObjectable(); + } + if (resourceType != null) { + qualifier = " on " + resourceType.getName(); + } else { + qualifier = " on resource " + shadowType.getResourceRef().getOid(); + } + } + + String referredObjectIdentification; + if (object != null) { + referredObjectIdentification = PolyString.getOrig(object.asObjectable().getName()) + + " (" + object.toDebugType() + ")" + + qualifier; + } else { + String nameOrOid = value.getTargetName() != null ? value.getTargetName().getOrig() : value.getOid(); + if (mightBeRemoved) { + referredObjectIdentification = "(cannot display the actual name of " + localPart(value.getTargetType()) + ":" + nameOrOid + ", as it might be already removed)"; + } else { + referredObjectIdentification = localPart(value.getTargetType()) + ":" + nameOrOid; + } + } + + return value.getRelation() != null ? + referredObjectIdentification + " [" + value.getRelation().getLocalPart() + "]" + : referredObjectIdentification; + } + + private PrismObject getPrismObject(String oid, boolean mightBeRemoved, OperationResult result) { + try { + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); + return (PrismObject) cacheRepositoryService.getObject(ObjectType.class, oid, options, result); + } catch (ObjectNotFoundException e) { + if (!mightBeRemoved) { + LoggingUtils.logException(LOGGER, "Couldn't resolve reference when displaying object name within a notification (it might be already removed)", e); + } else { + // ok, accepted + } + } catch (SchemaException e) { + LoggingUtils.logException(LOGGER, "Couldn't resolve reference when displaying object name within a notification", e); + } + return null; + } + + private String localPart(QName qname) { + return qname == null ? null : qname.getLocalPart(); + } + + // we call this on filtered list of item deltas - all of they have definition set + private String getItemDeltaLabel(ItemDelta itemDelta, PrismObjectDefinition objectDefinition) { + return getItemPathLabel(itemDelta.getPath(), itemDelta.getDefinition(), objectDefinition); + } + + private String getItemPathLabel(ItemPath path, Definition deltaDefinition, PrismObjectDefinition objectDefinition) { + + int lastNameIndex = path.lastNameIndex(); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < path.size(); i++) { + Object segment = path.getSegment(i); + if (ItemPath.isName(segment)) { + if (sb.length() > 0) { + sb.append("/"); + } + Definition itemDefinition; + if (objectDefinition == null) { + if (i == lastNameIndex) { // definition for last segment is the definition taken from delta + itemDefinition = deltaDefinition; // this may be null but we don't care + } else { + itemDefinition = null; // definitions for previous segments are unknown + } + } else { + // todo we could make this iterative (resolving definitions while walking down the path); but this is definitely simpler to implement and debug :) + itemDefinition = objectDefinition.findItemDefinition(path.allUpToIncluding(i)); + } + if (itemDefinition != null && itemDefinition.getDisplayName() != null) { + sb.append(resolve(itemDefinition.getDisplayName())); + } else { + sb.append(ItemPath.toName(segment).getLocalPart()); + } + } else if (ItemPath.isId(segment)) { + sb.append("[").append(ItemPath.toId(segment)).append("]"); + } + } + return sb.toString(); + } + + private String resolve(String key) { + if (key != null) { + return localizationService.translate(key, null, Locale.getDefault(), key); + } else { + return null; + } + } + + // we call this on filtered list of items - all of them have definition set + private String getItemLabel(Item item) { + return item.getDefinition().getDisplayName() != null ? + resolve(item.getDefinition().getDisplayName()) : item.getElementName().getLocalPart(); + } + + private List filterAndOrderItems(Collection items, List hiddenPaths, boolean showOperationalAttributes) { + if (items == null) { + return new ArrayList<>(); + } + List toBeDisplayed = new ArrayList<>(items.size()); + List noDefinition = new ArrayList<>(); + for (Item item : items) { + if (item.getDefinition() != null) { + boolean isHidden = NotificationFunctionsImpl.isAmongHiddenPaths(item.getPath(), hiddenPaths); + if (!isHidden && (showOperationalAttributes || !item.getDefinition().isOperational()) && !item.isEmpty()) { + toBeDisplayed.add(item); + } + } else { + noDefinition.add(item.getElementName()); + } + } + if (!noDefinition.isEmpty()) { + LOGGER.error("Items {} without definition - THEY WILL NOT BE INCLUDED IN NOTIFICATION.\nAll items:\n{}", + noDefinition, DebugUtil.debugDump(items)); + } + toBeDisplayed.sort((item1, item2) -> { + Integer order1 = item1.getDefinition().getDisplayOrder(); + Integer order2 = item2.getDefinition().getDisplayOrder(); + if (order1 != null && order2 != null) { + return order1 - order2; + } else if (order1 == null && order2 == null) { + return 0; + } else if (order1 == null) { + return 1; + } else { + return -1; + } + }); + return toBeDisplayed; + } + + public String formatUserName(SimpleObjectRef ref, OperationResult result) { + return formatUserName((UserType) ref.resolveObjectType(result, true), ref.getOid()); + } + + public String formatUserName(ObjectReferenceType ref, OperationResult result) { + UserType user = (UserType) functions.getObjectType(ref, true, result); + return formatUserName(user, ref.getOid()); + } + + public String formatUserName(UserType user, String oid) { + if (user == null || (user.getName() == null && user.getFullName() == null)) { + return oid; + } + if (user.getFullName() != null) { + return getOrig(user.getFullName()) + " (" + getOrig(user.getName()) + ")"; + } else { + return getOrig(user.getName()); + } + } + + // TODO implement seriously + public String formatDateTime(XMLGregorianCalendar timestamp) { + //DateFormatUtils.format(timestamp.toGregorianCalendar(), DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern()); + return String.valueOf(XmlTypeConverter.toDate(timestamp)); + } +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/GeneralNotifier.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/GeneralNotifier.java index 03157af41ad..05cfb87652b 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/GeneralNotifier.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/GeneralNotifier.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.notifications.impl.notifiers; +import com.evolveum.midpoint.notifications.impl.formatters.ValueFormatter; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.api.ProgressInformation; @@ -51,17 +52,11 @@ public class GeneralNotifier extends BaseHandler { private static final Trace DEFAULT_LOGGER = TraceManager.getTrace(GeneralNotifier.class); - @Autowired - protected NotificationManager notificationManager; - - @Autowired - protected NotificationFunctionsImpl functions; - - @Autowired - protected TextFormatter textFormatter; - - @Autowired - protected AggregatedEventHandler aggregatedEventHandler; + @Autowired protected NotificationManager notificationManager; + @Autowired protected NotificationFunctionsImpl functions; + @Autowired protected TextFormatter textFormatter; + @Autowired protected ValueFormatter valueFormatter; + @Autowired protected AggregatedEventHandler aggregatedEventHandler; @PostConstruct public void init() { diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleReviewerNotifier.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleReviewerNotifier.java index 135ff7573f6..98ec76c20b9 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleReviewerNotifier.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleReviewerNotifier.java @@ -39,11 +39,8 @@ public class SimpleReviewerNotifier extends GeneralNotifier { private static final Trace LOGGER = TraceManager.getTrace(SimpleReviewerNotifier.class); - @Autowired - private MidpointFunctions midpointFunctions; - - @Autowired - private CertHelper certHelper; + @Autowired private MidpointFunctions midpointFunctions; + @Autowired private CertHelper certHelper; @PostConstruct public void init() { @@ -97,9 +94,9 @@ protected String getBody(Event event, GeneralNotifierType generalNotifierType, S body.append("You have been requested to provide a review in a certification campaign."); body.append("\n"); - body.append("\nReviewer: ").append(textFormatter.formatUserName(reviewEvent.getActualReviewer(), result)); + body.append("\nReviewer: ").append(valueFormatter.formatUserName(reviewEvent.getActualReviewer(), result)); if (!reviewEvent.getActualReviewer().getOid().equals(reviewEvent.getRequesteeOid())) { - body.append("\nDeputy: ").append(textFormatter.formatUserName(reviewEvent.getRequestee(), result)); + body.append("\nDeputy: ").append(valueFormatter.formatUserName(reviewEvent.getRequestee(), result)); } body.append("\n"); body.append("\nCampaign: ").append(certHelper.getCampaignNameAndOid(reviewEvent)); diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java index 03d7bd50b79..6d282468c46 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java @@ -8,7 +8,6 @@ package com.evolveum.midpoint.notifications.impl.notifiers; import com.evolveum.midpoint.notifications.api.events.*; -import com.evolveum.midpoint.notifications.impl.formatters.TextFormatter; import com.evolveum.midpoint.prism.util.PrismUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.result.OperationResult; @@ -185,11 +184,11 @@ private boolean appendResultInformation(StringBuilder body, WorkflowEvent workfl private void appendDeadlineInformation(StringBuilder sb, WorkItemEvent event) { CaseWorkItemType workItem = event.getWorkItem(); if (!isDone(event) && workItem.getDeadline() != null) { - appendDeadlineInformation(sb, workItem, textFormatter); + appendDeadlineInformation(sb, workItem); } } - static void appendDeadlineInformation(StringBuilder sb, AbstractWorkItemType workItem, TextFormatter textFormatter) { + void appendDeadlineInformation(StringBuilder sb, AbstractWorkItemType workItem) { XMLGregorianCalendar deadline = workItem.getDeadline(); long before = XmlTypeConverter.toMillis(deadline) - System.currentTimeMillis(); long beforeRounded = Math.round((double) before / 60000.0) * 60000L; @@ -202,7 +201,7 @@ static void appendDeadlineInformation(StringBuilder sb, AbstractWorkItemType wor } else { beforePhrase = ""; } - sb.append("Deadline: ").append(textFormatter.formatDateTime(deadline)).append(beforePhrase).append("\n"); + sb.append("Deadline: ").append(valueFormatter.formatDateTime(deadline)).append(beforePhrase).append("\n"); sb.append("\n"); } @@ -224,7 +223,7 @@ private void appendResultAndOriginInformation(StringBuilder sb, WorkItemEvent ev SimpleObjectRef initiator = event.getInitiator(); if (initiator != null && !isCancelled(event)) { UserType initiatorFull = (UserType) functions.getObjectType(initiator, true, result); - sb.append("Carried out by: ").append(textFormatter.formatUserName(initiatorFull, initiator.getOid())).append("\n"); + sb.append("Carried out by: ").append(valueFormatter.formatUserName(initiatorFull, initiator.getOid())).append("\n"); atLeastOne = true; } } @@ -241,7 +240,7 @@ private void appendAssigneeInformation(StringBuilder sb, WorkItemEvent event, Op if (currentAssignees.size() != 1 || !java.util.Objects.equals(originalAssignee.getOid(), currentAssignees.get(0).getOid())) { UserType originalAssigneeObject = (UserType) functions.getObjectType(originalAssignee, true, result); sb.append("Originally allocated to: ").append( - textFormatter.formatUserName(originalAssigneeObject, originalAssignee.getOid())).append("\n"); + valueFormatter.formatUserName(originalAssigneeObject, originalAssignee.getOid())).append("\n"); atLeastOne = true; } if (!workItem.getAssigneeRef().isEmpty()) { @@ -253,7 +252,7 @@ private void appendAssigneeInformation(StringBuilder sb, WorkItemEvent event, Op } sb.append(": "); sb.append(workItem.getAssigneeRef().stream() - .map(ref -> textFormatter.formatUserName(ref, result)) + .map(ref -> valueFormatter.formatUserName(ref, result)) .collect(Collectors.joining(", "))); sb.append("\n"); atLeastOne = true;