Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split TextFormatter to Value- and DeltaFormatter
Preparing for MID-5849 and MID-5350 resolution.
- Loading branch information
Showing
6 changed files
with
690 additions
and
540 deletions.
There are no files selected for viewing
275 changes: 275 additions & 0 deletions
275
...mpl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/DeltaFormatter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<? extends Objectable> objectDelta, List<ItemPath> 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<? extends Objectable> objectDelta, List<ItemPath> 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<ItemDelta> 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<ItemDelta> deltas, PrismObjectDefinition objectDefinition, PrismObject objectOld, PrismObject objectNew, List<ItemPath> hiddenPaths, boolean showOperationalAttributes) { | ||
if (objectOld == null && objectNew == null) { | ||
return; // no data - no point in trying | ||
} | ||
boolean first = true; | ||
List<ItemPath> 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<ItemPath> 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<? extends PrismValue> values, | ||
boolean isDelete, ItemPath path, PrismObject objectOld, | ||
List<ItemPath> 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<ItemDelta> filterAndOrderItemDeltas(ObjectDelta<? extends Objectable> objectDelta, List<ItemPath> hiddenPaths, boolean showOperationalAttributes) { | ||
List<ItemDelta> toBeDisplayed = new ArrayList<>(objectDelta.getModifications().size()); | ||
List<QName> 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; | ||
} | ||
} |
Oops, something went wrong.