/* * Copyright (c) 2010-2016 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.web.component.prism; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.ReferenceDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; 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.resource.capabilities_3.ActivationCapabilityType; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.commons.lang.math.NumberUtils; import org.jetbrains.annotations.Nullable; import javax.xml.namespace.QName; import java.io.Serializable; import java.util.*; /** * @author lazyman */ public class ContainerWrapper<C extends Containerable> implements ItemWrapper, Serializable, DebugDumpable { private static final Trace LOGGER = TraceManager.getTrace(ContainerWrapper.class); private String displayName; private ObjectWrapper<? extends ObjectType> objectWrapper; private PrismContainer<C> container; private ContainerStatus status; private boolean main; private ItemPath path; private List<ItemWrapper> properties; private boolean readonly; private boolean showInheritedObjectAttributes; private PrismContainerDefinition<C> containerDefinition; ContainerWrapper(ObjectWrapper objectWrapper, PrismContainer<C> container, ContainerStatus status, ItemPath path) { Validate.notNull(container, "container must not be null."); Validate.notNull(status, "Container status must not be null."); this.objectWrapper = objectWrapper; this.container = container; this.status = status; this.path = path; this.main = path == null; this.readonly = objectWrapper.isReadonly(); // [pm] this is quite questionable this.showInheritedObjectAttributes = objectWrapper.isShowInheritedObjectAttributes(); // have to be after setting "main" property this.containerDefinition = getItemDefinition(); } ContainerWrapper(PrismContainer<C> container, ContainerStatus status, ItemPath path, boolean readOnly) { Validate.notNull(container, "container must not be null."); Validate.notNull(container.getDefinition(), "container definition must not be null."); Validate.notNull(status, "Container status must not be null."); this.container = container; this.containerDefinition = container.getDefinition(); this.status = status; this.path = path; this.main = path == null; this.readonly = readOnly; this.showInheritedObjectAttributes = false; } public void revive(PrismContext prismContext) throws SchemaException { if (container != null) { container.revive(prismContext); } if (containerDefinition != null) { containerDefinition.revive(prismContext); } if (properties != null) { for (ItemWrapper itemWrapper : properties) { itemWrapper.revive(prismContext); } } } @Override public PrismContainerDefinition<C> getItemDefinition() { if (containerDefinition != null) { return containerDefinition; } if (main) { return objectWrapper.getDefinition(); } else { return objectWrapper.getDefinition().findContainerDefinition(path); } } @Nullable ObjectWrapper getObject() { return objectWrapper; } public ContainerStatus getStatus() { return status; } public ItemPath getPath() { return path; } public PrismContainer<C> getItem() { return container; } public List<ItemWrapper> getItems() { if (properties == null) { properties = new ArrayList<>(); } return properties; } public void setProperties(List<ItemWrapper> properties) { this.properties = properties; } public <IW extends ItemWrapper> IW findPropertyWrapper(QName name) { Validate.notNull(name, "QName must not be null."); for (ItemWrapper wrapper : getItems()) { if (QNameUtil.match(name, wrapper.getItem().getElementName())) { return (IW) wrapper; } } return null; } // TODO: refactor this. Why it is not in the itemWrapper? boolean isItemVisible(ItemWrapper item) { ItemDefinition def = item.getItemDefinition(); if (def.isIgnored() || def.isOperational()) { return false; } if (def instanceof PrismPropertyDefinition && skipProperty((PrismPropertyDefinition) def)) { return false; } // we decide not according to status of this container, but according to // the status of the whole object if (objectWrapper != null && objectWrapper.getStatus() == ContainerStatus.ADDING) { return (def.canAdd() && def.isEmphasized()) || (def.canAdd() && showEmpty(item)); } // otherwise, object.getStatus() is MODIFYING if (def.canModify()) { return showEmpty(item); } else { if (def.canRead()) { return showEmpty(item); } return false; } } public void computeStripes() { int visibleProperties = 0; for (ItemWrapper item: properties) { if (item.isVisible()) { visibleProperties++; } if (visibleProperties % 2 == 0) { item.setStripe(true); } else { item.setStripe(false); } } } public boolean isShowInheritedObjectAttributes() { return showInheritedObjectAttributes; } private boolean showEmpty(ItemWrapper item) { // make sure that emphasized state is evaluated after the normal definitions are considered // we do not want to display emphasized property if the user does not have an access to it. // MID-3206 if (item.getItemDefinition().isEmphasized()) { return true; } ObjectWrapper objectWrapper = getObject(); List<ValueWrapper> valueWrappers = item.getValues(); boolean isEmpty; if (valueWrappers == null) { isEmpty = true; } else { isEmpty = valueWrappers.isEmpty(); } if (!isEmpty && valueWrappers.size() == 1) { ValueWrapper value = valueWrappers.get(0); if (ValueStatus.ADDED.equals(value.getStatus())) { isEmpty = true; } } return (objectWrapper == null || objectWrapper.isShowEmpty()) || !isEmpty; } @Override public String getDisplayName() { if (StringUtils.isNotEmpty(displayName)) { return displayName; } return getDisplayNameFromItem(container); } @Override public void setDisplayName(String name) { this.displayName = name; } @Override public QName getName() { return getItem().getElementName(); } public boolean isMain() { return main; } public void setMain(boolean main) { this.main = main; } static String getDisplayNameFromItem(Item item) { Validate.notNull(item, "Item must not be null."); String displayName = item.getDisplayName(); if (StringUtils.isEmpty(displayName)) { QName name = item.getElementName(); if (name != null) { displayName = name.getLocalPart(); } else { displayName = item.getDefinition().getTypeName().getLocalPart(); } } return displayName; } public boolean hasChanged() { for (ItemWrapper item : getItems()) { if (item.hasChanged()) { return true; } } return false; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ContainerWrapper("); builder.append(getDisplayNameFromItem(container)); builder.append(" ("); builder.append(status); builder.append(") "); builder.append(getItems() == null ? null : getItems().size()); builder.append(" items)"); return builder.toString(); } /** * This methods check if we want to show property in form (e.g. * failedLogins, fetchResult, lastFailedLoginTimestamp must be invisible) * * @return * @deprecated will be implemented through annotations in schema */ @Deprecated private boolean skipProperty(PrismPropertyDefinition def) { final List<QName> names = new ArrayList<QName>(); names.add(PasswordType.F_FAILED_LOGINS); names.add(PasswordType.F_LAST_FAILED_LOGIN); names.add(PasswordType.F_LAST_SUCCESSFUL_LOGIN); names.add(PasswordType.F_PREVIOUS_SUCCESSFUL_LOGIN); names.add(ObjectType.F_FETCH_RESULT); // activation names.add(ActivationType.F_EFFECTIVE_STATUS); names.add(ActivationType.F_VALIDITY_STATUS); // user names.add(UserType.F_RESULT); // org and roles names.add(OrgType.F_APPROVAL_PROCESS); names.add(OrgType.F_APPROVER_EXPRESSION); names.add(OrgType.F_AUTOMATICALLY_APPROVED); names.add(OrgType.F_CONDITION); for (QName name : names) { if (name.equals(def.getName())) { return true; } } return false; } public boolean isReadonly() { // readonly flag in container is an override. Do not get the value from definition // otherwise it will be propagated to items and overrides the item definition. return readonly; } public void setReadonly(boolean readonly) { this.readonly = readonly; } @Override public List<ValueWrapper> getValues() { // TODO Auto-generated method stub return null; } @Override public boolean isVisible() { // TODO Auto-generated method stub return false; } @Override public boolean isEmpty() { return getItem().isEmpty(); } @Override public ContainerWrapper<C> getContainer() { // TODO Auto-generated method stub return null; } //TODO add new PrismContainerValue to association container public void addValue() { getItems().add(createItem()); } public ItemWrapper createItem() { ValueWrapper wrapper = new ValueWrapper(this, new PrismPropertyValue(null), ValueStatus.ADDED); return wrapper.getItem(); } public void sort(final PageBase pageBase) { if (objectWrapper.isSorted()){ Collections.sort(properties, new Comparator<ItemWrapper>(){ @Override public int compare(ItemWrapper pw1, ItemWrapper pw2) { ItemDefinition id1 = pw1.getItemDefinition(); ItemDefinition id2 = pw2.getItemDefinition(); String str1 =(id1 != null ? (id1.getDisplayName() != null ? (pageBase.createStringResource(id1.getDisplayName()) != null && StringUtils.isNotEmpty(pageBase.createStringResource(id1.getDisplayName()).getString()) ? pageBase.createStringResource(id1.getDisplayName()).getString() : id1.getDisplayName()): (id1.getName() != null && id1.getName().getLocalPart() != null ? id1.getName().getLocalPart() : "")) : ""); String str2 =(id2 != null ? (id2.getDisplayName() != null ? (pageBase.createStringResource(id2.getDisplayName()) != null && StringUtils.isNotEmpty(pageBase.createStringResource(id2.getDisplayName()).getString()) ? pageBase.createStringResource(id2.getDisplayName()).getString() : id2.getDisplayName()): (id2.getName() != null && id2.getName().getLocalPart() != null ? id2.getName().getLocalPart() : "")) : ""); return str1.compareToIgnoreCase(str2); } }); } else { final int[] maxOrderArray = new int[3]; Collections.sort(properties, new Comparator<ItemWrapper>(){ @Override public int compare(ItemWrapper pw1, ItemWrapper pw2) { ItemDefinition id1 = pw1.getItemDefinition(); ItemDefinition id2 = pw2.getItemDefinition(); //we need to find out the value of the biggest displayOrder to put //properties with null display order to the end of the list int displayOrder1 = (id1 != null && id1.getDisplayOrder() != null) ? id1.getDisplayOrder() : 0; int displayOrder2 = (id2 != null && id2.getDisplayOrder() != null) ? id2.getDisplayOrder() : 0; if (maxOrderArray[0] == 0){ maxOrderArray[0] = displayOrder1 > displayOrder2 ? displayOrder1 + 1 : displayOrder2 + 1; } maxOrderArray[1] = displayOrder1; maxOrderArray[2] = displayOrder2; int maxDisplayOrder = NumberUtils.max(maxOrderArray); maxOrderArray[0] = maxDisplayOrder + 1; return Integer.compare(id1 != null && id1.getDisplayOrder() != null ? id1.getDisplayOrder() : maxDisplayOrder, id2 != null && id2.getDisplayOrder() != null ? id2.getDisplayOrder() : maxDisplayOrder); } }); } } @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); sb.append("ContainerWrapper: ").append(PrettyPrinter.prettyPrint(getName())).append("\n"); DebugUtil.debugDumpWithLabel(sb, "displayName", displayName, indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "status", status == null ? null : status.toString(), indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "main", main, indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "readonly", readonly, indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "showInheritedObjectAttributes", showInheritedObjectAttributes, indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "path", path == null ? null : path.toString(), indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "containerDefinition", containerDefinition == null ? null : containerDefinition.toString(), indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "container", container == null ? null : container.toString(), indent + 1); sb.append("\n"); DebugUtil.debugDumpLabel(sb, "properties", indent + 1); sb.append("\n"); DebugUtil.debugDump(sb, properties, indent + 2, false); return sb.toString(); } @Override public boolean isStripe() { // Does not make much sense, but it is given by the interface return false; } @Override public void setStripe(boolean isStripe) { // Does not make much sense, but it is given by the interface } public <O extends ObjectType> void collectModifications(ObjectDelta<O> delta) throws SchemaException { if (getItemDefinition().getName().equals(ShadowType.F_ASSOCIATION)) { //create ContainerDelta for association container //HACK HACK HACK create correct procession for association container data //according to its structure ContainerDelta<ShadowAssociationType> associationDelta = ContainerDelta.createDelta(ShadowType.F_ASSOCIATION, (PrismContainerDefinition<ShadowAssociationType>) getItemDefinition()); for (ItemWrapper itemWrapper : getItems()) { AssociationWrapper associationItemWrapper = (AssociationWrapper) itemWrapper; List<ValueWrapper> assocValueWrappers = associationItemWrapper.getValues(); for (ValueWrapper assocValueWrapper : assocValueWrappers) { PrismContainerValue<ShadowAssociationType> assocValue = (PrismContainerValue<ShadowAssociationType>) assocValueWrapper.getValue(); if (!assocValue.isEmpty()) { if (assocValueWrapper.getStatus() == ValueStatus.DELETED) { associationDelta.addValueToDelete(assocValue.clone()); } else if (assocValueWrapper.getStatus().equals(ValueStatus.ADDED)) { associationDelta.addValueToAdd(assocValue.clone()); } } } } if (!associationDelta.isEmpty()) { delta.addModification(associationDelta); } } else { if (!hasChanged()) { return; } for (ItemWrapper itemWrapper : getItems()) { if (!itemWrapper.hasChanged()) { continue; } ItemPath containerPath = getPath() != null ? getPath() : new ItemPath(); if (itemWrapper instanceof PropertyWrapper) { ItemDelta pDelta = computePropertyDeltas((PropertyWrapper) itemWrapper, containerPath); if (!pDelta.isEmpty()) { //HACK to remove a password replace delta is to be created if (getName().equals(CredentialsType.F_PASSWORD)) { if (pDelta.getValuesToDelete() != null){ pDelta.resetValuesToDelete(); pDelta.setValuesToReplace(new ArrayList()); } } delta.addModification(pDelta); } } else if (itemWrapper instanceof ReferenceWrapper) { ReferenceDelta pDelta = computeReferenceDeltas((ReferenceWrapper) itemWrapper, containerPath); if (!pDelta.isEmpty()) { delta.addModification(pDelta); } } else { LOGGER.trace("Delta from wrapper: ignoring {}", itemWrapper); } } } } private ItemDelta computePropertyDeltas(PropertyWrapper propertyWrapper, ItemPath containerPath) { ItemDefinition itemDef = propertyWrapper.getItemDefinition(); ItemDelta pDelta = itemDef.createEmptyDelta(containerPath.subPath(itemDef.getName())); addItemDelta(propertyWrapper, pDelta, itemDef, containerPath); return pDelta; } private ReferenceDelta computeReferenceDeltas(ReferenceWrapper referenceWrapper, ItemPath containerPath) { PrismReferenceDefinition propertyDef = referenceWrapper.getItemDefinition(); ReferenceDelta pDelta = new ReferenceDelta(containerPath, propertyDef.getName(), propertyDef, propertyDef.getPrismContext()); addItemDelta(referenceWrapper, pDelta, propertyDef, containerPath.subPath(propertyDef.getName())); return pDelta; } private void addItemDelta(ItemWrapper<? extends Item, ? extends ItemDefinition> itemWrapper, ItemDelta pDelta, ItemDefinition propertyDef, ItemPath containerPath) { for (ValueWrapper valueWrapper : itemWrapper.getValues()) { valueWrapper.normalize(propertyDef.getPrismContext()); ValueStatus valueStatus = valueWrapper.getStatus(); if (!valueWrapper.hasValueChanged() && (ValueStatus.NOT_CHANGED.equals(valueStatus) || ValueStatus.ADDED.equals(valueStatus))) { continue; } // TODO: need to check if the resource has defined // capabilities // todo this is bad hack because now we have not tri-state // checkbox if (SchemaConstants.PATH_ACTIVATION.equivalent(containerPath) && getObject() != null) { PrismObject<?> object = getObject().getObject(); if (object.asObjectable() instanceof ShadowType && (((ShadowType) object.asObjectable()).getActivation() == null || ((ShadowType) object .asObjectable()).getActivation().getAdministrativeStatus() == null)) { if (!getObject().hasResourceCapability(((ShadowType) object.asObjectable()).getResource(), ActivationCapabilityType.class)) { continue; } } } PrismValue newValCloned = ObjectWrapper.clone(valueWrapper.getValue()); PrismValue oldValCloned = ObjectWrapper.clone(valueWrapper.getOldValue()); switch (valueWrapper.getStatus()) { case ADDED: if (newValCloned != null) { if (SchemaConstants.PATH_PASSWORD.equivalent(containerPath)) { // password change will always look like add, // therefore we push replace if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (password) ADD -> replace {}", pDelta.getPath(), newValCloned); } pDelta.setValuesToReplace(Arrays.asList(newValCloned)); } else if (propertyDef.isSingleValue()) { // values for single-valued properties // should be pushed via replace // in order to prevent problems e.g. with // summarizing deltas for // unreachable resources if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (single,new) ADD -> replace {}", pDelta.getPath(), newValCloned); } pDelta.setValueToReplace(newValCloned); } else { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (multi,new) ADD -> add {}", pDelta.getPath(), newValCloned); } pDelta.addValueToAdd(newValCloned); } } break; case DELETED: if (newValCloned != null) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (new) DELETE -> delete {}", pDelta.getPath(), newValCloned); } pDelta.addValueToDelete(newValCloned); } if (oldValCloned != null) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (old) DELETE -> delete {}", pDelta.getPath(), oldValCloned); } pDelta.addValueToDelete(oldValCloned); } break; case NOT_CHANGED: // this is modify... if (propertyDef.isSingleValue()) { // newValCloned.isEmpty() if (newValCloned != null && !newValCloned.isEmpty()) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (single,new) NOT_CHANGED -> replace {}", pDelta.getPath(), newValCloned); } pDelta.setValuesToReplace(Arrays.asList(newValCloned)); } else { if (oldValCloned != null) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (single,old) NOT_CHANGED -> delete {}", pDelta.getPath(), oldValCloned); } pDelta.addValueToDelete(oldValCloned); } } } else { if (newValCloned != null && !newValCloned.isEmpty()) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (multi,new) NOT_CHANGED -> add {}", pDelta.getPath(), newValCloned); } pDelta.addValueToAdd(newValCloned); } if (oldValCloned != null) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Delta from wrapper: {} (multi,old) NOT_CHANGED -> delete {}", pDelta.getPath(), oldValCloned); } pDelta.addValueToDelete(oldValCloned); } } break; } } } }