com.evolveum.midpoint.notifications.impl.formatters.TextFormatter.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.notifications.impl.formatters.TextFormatter.java

Source

/*
 * 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.impl.formatters;

import com.evolveum.midpoint.notifications.impl.NotificationsUtil;
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.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ValueDisplayUtil;
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.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;

import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.xml.namespace.QName;

import java.util.*;

/**
 * @author mederly
 */
@Component
public class TextFormatter {

    @Autowired(required = true)
    @Qualifier("cacheRepositoryService")
    private transient RepositoryService cacheRepositoryService;

    private static final Trace LOGGER = TraceManager.getTrace(TextFormatter.class);

    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(ObjectDelta<? extends Objectable> objectDelta,
            List<ItemPath> hiddenPaths, boolean showOperationalAttributes, PrismObject objectOld,
            PrismObject objectNew) {
        Validate.notNull(objectDelta, "objectDelta is null");
        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;
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("formatObjectModificationDelta: objectDelta = " + objectDelta.debugDump()
                    + ", hiddenPaths = " + PrettyPrinter.prettyPrint(hiddenPaths));
        }

        StringBuilder retval = new StringBuilder();

        List<ItemDelta> toBeDisplayed = filterAndOrderItemDeltas(objectDelta, hiddenPaths,
                showOperationalAttributes);
        for (ItemDelta itemDelta : toBeDisplayed) {
            retval.append(" - ");
            retval.append(getItemDeltaLabel(itemDelta, objectDefinition));
            retval.append(":\n");
            formatItemDeltaContent(retval, itemDelta, hiddenPaths, showOperationalAttributes);
        }

        explainPaths(retval, toBeDisplayed, objectDefinition, objectOld, objectNew, hiddenPaths,
                showOperationalAttributes);

        return retval.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 || ItemPath.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.",
                        new Object[] { 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",
                        new Object[] { 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, List<ItemPath> hiddenPaths,
            boolean showOperationalAttributes) {
        formatItemDeltaValues(sb, "ADD", itemDelta.getValuesToAdd(), false, hiddenPaths, showOperationalAttributes);
        formatItemDeltaValues(sb, "DELETE", itemDelta.getValuesToDelete(), true, hiddenPaths,
                showOperationalAttributes);
        formatItemDeltaValues(sb, "REPLACE", itemDelta.getValuesToReplace(), false, hiddenPaths,
                showOperationalAttributes);
    }

    private void formatItemDeltaValues(StringBuilder sb, String type, Collection<? extends PrismValue> values,
            boolean mightBeRemoved, List<ItemPath> hiddenPaths, boolean showOperationalAttributes) {
        if (values != null) {
            for (PrismValue prismValue : values) {
                sb.append("   - " + type + ": ");
                String prefix = "     ";
                formatPrismValue(sb, prefix, prismValue, mightBeRemoved, hiddenPaths, showOperationalAttributes);
                if (!(prismValue instanceof PrismContainerValue)) { // container values already end with newline
                    sb.append("\n");
                }
            }
        }
    }

    // todo - should each hiddenAttribute be prefixed with something like F_ATTRIBUTE? Currently it should not be.
    public String formatAccountAttributes(ShadowType shadowType, List<ItemPath> 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)
                    retval.append("\n");
                else
                    first = false;
                retval.append("Association:\n");
                formatContainerValue(retval, "  ", shadowAssociationType.asPrismContainerValue(), false,
                        hiddenAttributes, showOperationalAttributes);
                retval.append("\n");
            }
        }

        return retval.toString();
    }

    public String formatObject(PrismObject object, List<ItemPath> 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<ItemPath> 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<ItemPath> hiddenPaths, boolean showOperationalAttributes) {
        //        sb.append("Container of type " + containerValue.getParent().getDefinition().getTypeName());
        //        sb.append("\n");

        List<Item> 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);
            }
        }
    }

    private void formatPrismContainer(StringBuilder sb, String prefix, Item item, boolean mightBeRemoved,
            List<ItemPath> hiddenPaths, boolean showOperationalAttributes) {
        for (PrismContainerValue subContainerValue : ((PrismContainer<? extends Containerable>) item).getValues()) {
            sb.append(prefix);
            sb.append(" - ");
            sb.append(getItemLabel(item));
            if (subContainerValue.getId() != null) {
                sb.append(" #").append(subContainerValue.getId());
            }
            sb.append(":\n");
            String prefixSubContainer = prefix + "   ";
            formatContainerValue(sb, prefixSubContainer, subContainerValue, mightBeRemoved, hiddenPaths,
                    showOperationalAttributes);
        }
    }

    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 + "   - ");
                sb.append(formatReferenceValue(referenceValue, mightBeRemoved));
            }
        } else if (item.size() == 1) {
            sb.append(formatReferenceValue(((PrismReference) item).getValue(0), 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<? extends Object>) item).getValues()) {
                sb.append("\n");
                sb.append(prefix + "   - ");
                sb.append(ValueDisplayUtil.toStringValue(propertyValue));
            }
        } else if (item.size() == 1) {
            sb.append(ValueDisplayUtil.toStringValue(((PrismProperty<? extends Object>) item).getValue(0)));
        }
        sb.append("\n");
    }

    private String formatReferenceValue(PrismReferenceValue value, boolean mightBeRemoved) {

        OperationResult result = new OperationResult("dummy");

        PrismObject<? extends ObjectType> 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();
            ResourceType resourceType = shadowType.getResource();
            if (resourceType == null) {
                PrismObject<? extends ObjectType> resource = getPrismObject(shadowType.getResourceRef().getOid(),
                        false, result);
                if (resource != null) {
                    resourceType = (ResourceType) resource.asObjectable();
                }
            }
            if (resourceType != null) {
                qualifier = " on " + resourceType.getName();
            } else {
                qualifier = " on resource " + shadowType.getResourceRef().getOid();
            }
        }

        if (object != null) {
            return PolyString.getOrig(object.asObjectable().getName()) + " (" + object.toDebugType() + ")"
                    + qualifier;
        } else {
            if (mightBeRemoved) {
                return "(cannot display the name of " + localPart(value.getTargetType()) + ":" + value.getOid()
                        + ", as it might be already removed)";
            } else {
                return localPart(value.getTargetType()) + ":" + value.getOid();
            }
        }
    }

    private PrismObject<? extends ObjectType> getPrismObject(String oid, boolean mightBeRemoved,
            OperationResult result) {
        try {
            return cacheRepositoryService.getObject(ObjectType.class, oid, null, 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 {
            }
        } catch (SchemaException e) {
            LoggingUtils.logException(LOGGER,
                    "Couldn't resolve reference when displaying object name within a notification", e);
        }
        return null;
    }

    private String localPartOfType(Item item) {
        if (item.getDefinition() != null) {
            return localPart(item.getDefinition().getTypeName());
        } else {
            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) {

        NameItemPathSegment lastNamedSegment = path.lastNamed();

        StringBuilder sb = new StringBuilder();
        for (ItemPathSegment segment : path.getSegments()) {
            if (segment instanceof NameItemPathSegment) {
                if (sb.length() > 0) {
                    sb.append("/");
                }
                Definition itemDefinition;
                if (objectDefinition == null) {
                    if (segment == lastNamedSegment) { // 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(segment));
                }
                if (itemDefinition != null && itemDefinition.getDisplayName() != null) {
                    sb.append(itemDefinition.getDisplayName());
                } else {
                    sb.append(((NameItemPathSegment) segment).getName().getLocalPart());
                }
            } else if (segment instanceof IdItemPathSegment) {
                sb.append("[").append(((IdItemPathSegment) segment).getId()).append("]");
            }
        }
        return sb.toString();
    }

    // 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++) {
            ItemPathSegment segment = path.getSegments().get(i);
            if (segment instanceof IdItemPathSegment) {
                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<ItemDelta>(objectDelta.getModifications().size());
        for (ItemDelta itemDelta : objectDelta.getModifications()) {
            if (itemDelta.getDefinition() != null) {
                if ((showOperationalAttributes || !itemDelta.getDefinition().isOperational())
                        && !NotificationsUtil.isAmongHiddenPaths(itemDelta.getPath(), hiddenPaths)) {
                    toBeDisplayed.add(itemDelta);
                }
            } else {
                LOGGER.error("ItemDelta " + itemDelta.getElementName()
                        + " without definition - WILL NOT BE INCLUDED IN NOTIFICATION. In " + objectDelta);
            }
        }
        Collections.sort(toBeDisplayed, new Comparator<ItemDelta>() {
            @Override
            public int compare(ItemDelta delta1, ItemDelta 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 ? item.getDefinition().getDisplayName()
                : item.getElementName().getLocalPart();
    }

    private List<Item> filterAndOrderItems(List<Item> items, List<ItemPath> hiddenPaths,
            boolean showOperationalAttributes) {
        if (items == null) {
            return new ArrayList<>();
        }
        List<Item> toBeDisplayed = new ArrayList<Item>(items.size());
        for (Item item : items) {
            if (item.getDefinition() != null) {
                boolean isHidden = NotificationsUtil.isAmongHiddenPaths(item.getPath(), hiddenPaths);
                if (!isHidden && (showOperationalAttributes || !item.getDefinition().isOperational())) {
                    toBeDisplayed.add(item);
                }
            } else {
                LOGGER.error("Item " + item.getElementName()
                        + " without definition - WILL NOT BE INCLUDED IN NOTIFICATION.");
            }
        }
        Collections.sort(toBeDisplayed, new Comparator<Item>() {
            @Override
            public int compare(Item item1, Item 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;
    }

}