/*
* 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.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.*;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
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.CapabilityType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import java.io.Serializable;
import java.util.*;
/**
* @author lazyman
*/
public class ObjectWrapper<O extends ObjectType> implements Serializable, Revivable, DebugDumpable {
private static final long serialVersionUID = 1L;
public static final String F_DISPLAY_NAME = "displayName";
public static final String F_SELECTED = "selected";
private static final Trace LOGGER = TraceManager.getTrace(ObjectWrapper.class);
public static final String PROPERTY_CONTAINERS = "containers";
private PrismObject<O> object;
private PrismObject<O> objectOld;
private ObjectDelta<O> oldDelta;
private ContainerStatus status;
private HeaderStatus headerStatus;
private String displayName;
private String description;
private List<ContainerWrapper<? extends Containerable>> containers;
private boolean showEmpty;
private boolean minimalized;
private boolean sorted;
private boolean selectable;
private boolean selected;
private boolean showAssignments = false;
// whether to show name and description properties and metadata container
private boolean showInheritedObjectAttributes = true;
// readolny flag is an override. false means "do not override"
private boolean readonly = false;
private Collection<SelectorOptions<GetOperationOptions>> loadOptions;
private OperationResult result;
private Collection<PrismObject<OrgType>> parentOrgs = new ArrayList<>();
private OperationResult fetchResult;
// a "static" (non-refined) definition that reflects editability of the object in terms of midPoint schema limitations and security
private PrismContainerDefinition objectDefinitionForEditing;
// a refined definition of an resource object class that reflects its editability; applicable for shadows only
private RefinedObjectClassDefinition objectClassDefinitionForEditing;
public ObjectWrapper(String displayName, String description, PrismObject object,
PrismContainerDefinition objectDefinitionForEditing, ContainerStatus status) {
this(displayName, description, object, objectDefinitionForEditing, null, status, false);
}
// delayContainerCreation is used in cases where caller wants to configure
// those aspects of the wrapper that must be set before container creation
public ObjectWrapper(String displayName, String description, PrismObject object,
PrismContainerDefinition objectDefinitionForEditing,
RefinedObjectClassDefinition objectClassDefinitionForEditing, ContainerStatus status,
boolean delayContainerCreation) {
Validate.notNull(object, "Object must not be null.");
Validate.notNull(status, "Container status must not be null.");
this.displayName = displayName;
this.description = description;
this.object = object;
this.objectOld = object.clone();
this.status = status;
this.objectDefinitionForEditing = objectDefinitionForEditing;
this.objectClassDefinitionForEditing = objectClassDefinitionForEditing;
}
public void initializeContainers(PageBase pageBase) {
//todo remove
}
public void revive(PrismContext prismContext) throws SchemaException {
if (object != null) {
object.revive(prismContext);
}
if (oldDelta != null) {
oldDelta.revive(prismContext);
}
if (containers != null) {
for (ContainerWrapper containerWrapper : containers) {
containerWrapper.revive(prismContext);
}
}
}
public Collection<PrismObject<OrgType>> getParentOrgs() {
return parentOrgs;
}
public OperationResult getFetchResult() {
return fetchResult;
}
public void setFetchResult(OperationResult fetchResult) {
this.fetchResult = fetchResult;
}
void setResult(OperationResult result) {
this.result = result;
}
public OperationResult getResult() {
return result;
}
public void clearResult() {
result = null;
}
public Collection<SelectorOptions<GetOperationOptions>> getLoadOptions() {
return loadOptions;
}
public void setLoadOptions(Collection<SelectorOptions<GetOperationOptions>> loadOptions) {
this.loadOptions = loadOptions;
}
public HeaderStatus getHeaderStatus() {
if (headerStatus == null) {
headerStatus = HeaderStatus.NORMAL;
}
return headerStatus;
}
public ObjectDelta<O> getOldDelta() {
return oldDelta;
}
public void setOldDelta(ObjectDelta<O> oldDelta) {
this.oldDelta = oldDelta;
}
public void setHeaderStatus(HeaderStatus headerStatus) {
this.headerStatus = headerStatus;
}
public PrismObject<O> getObject() {
return object;
}
public PrismObject<O> getObjectOld() {
return objectOld;
}
public String getDisplayName() {
if (displayName == null) {
return WebComponentUtil.getName(object);
}
return displayName;
}
public ContainerStatus getStatus() {
return status;
}
public String getDescription() {
return description;
}
public boolean isMinimalized() {
return minimalized;
}
public void setMinimalized(boolean minimalized) {
this.minimalized = minimalized;
}
public boolean isSorted() {
return sorted;
}
public void setSorted(boolean sorted) {
this.sorted = sorted;
}
public boolean isShowEmpty() {
return showEmpty;
}
public void setShowEmpty(boolean showEmpty) {
this.showEmpty = showEmpty;
computeStripes();
}
public boolean isSelectable() {
return selectable;
}
public void setSelectable(boolean selectable) {
this.selectable = selectable;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public List<ContainerWrapper<? extends Containerable>> getContainers() {
if (containers == null) {
containers = new ArrayList<>();
}
return containers;
}
public void setContainers(List<ContainerWrapper<? extends Containerable>> containers) {
this.containers = containers;
}
public <C extends Containerable> ContainerWrapper<C> findContainerWrapper(ItemPath path) {
for (ContainerWrapper wrapper : getContainers()) {
if (path != null) {
if (path.equivalent(wrapper.getPath())) {
return wrapper;
}
} else {
if (wrapper.getPath() == null) {
return wrapper;
}
}
}
return null;
}
public ContainerWrapper<O> findMainContainerWrapper() {
for (ContainerWrapper wrapper : getContainers()) {
if (wrapper.isMain()) {
return wrapper;
}
}
return null;
}
public <IW extends ItemWrapper> IW findPropertyWrapper(ItemPath path) {
ContainerWrapper containerWrapper;
ItemPath propertyPath;
if (path.size() == 1) {
containerWrapper = findMainContainerWrapper();
propertyPath = path;
} else {
containerWrapper = findContainerWrapper(path.head());
propertyPath = path.tail();
}
if (containerWrapper == null) {
return null;
}
return (IW) containerWrapper.findPropertyWrapper(ItemPath.getFirstName(propertyPath));
}
public void normalize() throws SchemaException {
ObjectDelta delta = getObjectDelta();
if (ChangeType.ADD.equals(delta.getChangeType())) {
object = delta.getObjectToAdd();
} else {
delta.applyTo(object);
}
}
public void sort(PageBase pageBase) {
ContainerWrapper main = findMainContainerWrapper();
if (main != null) {
main.sort(pageBase);
}
computeStripes();
}
private void computeStripes() {
for (ContainerWrapper<? extends Containerable> container: containers) {
container.computeStripes();
}
}
public ObjectDelta<O> getObjectDelta() throws SchemaException {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Wrapper before creating delta:\n{}", this.debugDump());
}
if (ContainerStatus.ADDING.equals(getStatus())) {
return createAddingObjectDelta();
}
ObjectDelta<O> delta = new ObjectDelta<O>(object.getCompileTimeClass(), ChangeType.MODIFY,
object.getPrismContext());
delta.setOid(object.getOid());
List<ContainerWrapper<? extends Containerable>> containers = getContainers();
// sort containers by path size
Collections.sort(containers, new PathSizeComparator());
for (ContainerWrapper containerWrapper : getContainers()) {
containerWrapper.collectModifications(delta);
}
// returning container to previous order
Collections.sort(containers, new ItemWrapperComparator());
if (object.getPrismContext() != null) {
// Make sure we have all the definitions
object.getPrismContext().adopt(delta);
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Creating delta from wrapper {}: existing object, creating delta:\n{}", this, delta.debugDump());
}
return delta;
}
// TODO move to appropriate place!
public static PrismValue clone(PrismValue value) {
if (value == null) {
return null;
}
PrismValue cloned = value.clone();
cloned.setOriginType(OriginType.USER_ACTION);
if (value instanceof PrismPropertyValue) {
PrismPropertyValue ppValue = (PrismPropertyValue) value;
if (ppValue.getValue() instanceof ProtectedStringType) {
((PrismPropertyValue) cloned).setValue(((ProtectedStringType) ppValue.getValue()).clone());
}
if (ppValue.getValue() instanceof PolyString) {
PolyString poly = (PolyString) ppValue.getValue();
if (StringUtils.isEmpty(poly.getOrig())) {
return null;
}
((PrismPropertyValue) cloned).setValue(new PolyString(poly.getOrig(), poly.getNorm()));
}
} else if (value instanceof PrismReferenceValue) {
if (cloned == null) {
return null;
}
if (cloned.isEmpty()) {
return null;
}
}
return cloned;
}
protected boolean hasResourceCapability(ResourceType resource,
Class<? extends CapabilityType> capabilityClass) {
if (resource == null) {
return false;
}
return ResourceTypeUtil.hasEffectiveCapability(resource, capabilityClass);
}
private ObjectDelta createAddingObjectDelta() throws SchemaException {
PrismObject object = this.object.clone();
List<ContainerWrapper<? extends Containerable>> containers = getContainers();
// sort containers by path size
Collections.sort(containers, new PathSizeComparator());
for (ContainerWrapper containerWrapper : getContainers()) {
if (containerWrapper.getItemDefinition().getName().equals(ShadowType.F_ASSOCIATION)) {
PrismContainer associationContainer = object.findOrCreateContainer(ShadowType.F_ASSOCIATION);
List<AssociationWrapper> associationItemWrappers = (List<AssociationWrapper>) containerWrapper.getItems();
for (AssociationWrapper associationItemWrapper : associationItemWrappers) {
List<ValueWrapper> assocValueWrappers = associationItemWrapper.getValues();
for (ValueWrapper assocValueWrapper : assocValueWrappers) {
PrismContainerValue<ShadowAssociationType> assocValue = (PrismContainerValue<ShadowAssociationType>) assocValueWrapper.getValue();
associationContainer.add(assocValue.clone());
}
}
continue;
}
if (!containerWrapper.hasChanged()) {
continue;
}
PrismContainer container = containerWrapper.getItem();
ItemPath path = containerWrapper.getPath();
if (containerWrapper.getPath() != null) {
container = container.clone();
if (path.size() > 1) {
ItemPath parentPath = path.allExceptLast();
PrismContainer parent = object.findOrCreateContainer(parentPath);
parent.add(container);
} else {
PrismContainer existing = object.findContainer(container.getElementName());
if (existing == null) {
object.add(container);
} else {
continue;
}
}
} else {
container = object;
}
for (ItemWrapper itemWrapper : (List<ItemWrapper>) containerWrapper.getItems()) {
if (!itemWrapper.hasChanged()) {
continue;
}
if (container.findItem(itemWrapper.getName()) != null) {
continue;
}
Item updatedItem = ((PropertyOrReferenceWrapper) itemWrapper).getUpdatedItem(object.getPrismContext());
if (!updatedItem.isEmpty()) {
container.add(updatedItem);
}
}
}
// cleanup empty containers
cleanupEmptyContainers(object);
ObjectDelta delta = ObjectDelta.createAddDelta(object);
// returning container to previous order
Collections.sort(containers, new ItemWrapperComparator());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Creating delta from wrapper {}: adding object, creating complete ADD delta:\n{}", this, delta.debugDump());
}
if (InternalsConfig.consistencyChecks) {
delta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH);
}
return delta;
}
private void cleanupEmptyContainers(PrismContainer container) {
List<PrismContainerValue> values = container.getValues();
List<PrismContainerValue> valuesToBeRemoved = new ArrayList<PrismContainerValue>();
for (PrismContainerValue value : values) {
List<? extends Item> items = value.getItems();
if (items != null) {
Iterator<? extends Item> iterator = items.iterator();
while (iterator.hasNext()) {
Item item = iterator.next();
if (item instanceof PrismContainer) {
cleanupEmptyContainers((PrismContainer) item);
if (item.isEmpty()) {
iterator.remove();
}
}
}
}
if (items == null || value.isEmpty()) {
valuesToBeRemoved.add(value);
}
}
container.removeAll(valuesToBeRemoved);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ObjectWrapper(");
builder.append(ContainerWrapper.getDisplayNameFromItem(object));
builder.append(" (");
builder.append(status);
builder.append(") ");
builder.append(getContainers() == null ? null : getContainers().size());
builder.append(" containers)");
return builder.toString();
}
public boolean isProtectedAccount() {
if (object == null || !(ShadowType.class.isAssignableFrom(object.getCompileTimeClass()))) {
return false;
}
PrismProperty<Boolean> protectedObject = object.findProperty(ShadowType.F_PROTECTED_OBJECT);
if (protectedObject == null) {
return false;
}
return protectedObject.getRealValue() != null ? protectedObject.getRealValue() : false;
}
private static class PathSizeComparator implements Comparator<ContainerWrapper> {
@Override
public int compare(ContainerWrapper c1, ContainerWrapper c2) {
int size1 = c1.getPath() != null ? c1.getPath().size() : 0;
int size2 = c2.getPath() != null ? c2.getPath().size() : 0;
return size1 - size2;
}
}
public String getOid() {
return object.getOid();
}
public boolean isShowAssignments() {
return showAssignments;
}
public void setShowAssignments(boolean showAssignments) {
this.showAssignments = showAssignments;
}
public boolean isReadonly() {
if (isProtectedAccount()) {
return true;
}
return readonly;
}
public void setReadonly(boolean readonly) {
this.readonly = readonly;
}
public boolean isShowInheritedObjectAttributes() {
return showInheritedObjectAttributes;
}
public void setShowInheritedObjectAttributes(boolean showInheritedObjectAttributes) {
this.showInheritedObjectAttributes = showInheritedObjectAttributes;
}
public PrismContainerDefinition getDefinition() {
if (objectDefinitionForEditing != null) {
return objectDefinitionForEditing;
}
return object.getDefinition();
}
public PrismContainerDefinition getRefinedAttributeDefinition() {
if (objectClassDefinitionForEditing != null) {
return objectClassDefinitionForEditing.toResourceAttributeContainerDefinition();
}
return null;
}
public void copyRuntimeStateTo(ObjectWrapper<O> newWrapper) {
newWrapper.setMinimalized(this.isMinimalized());
newWrapper.setShowEmpty(this.isShowEmpty());
newWrapper.setSorted(this.isSorted());
newWrapper.setSelectable(this.isSelectable());
newWrapper.setSelected(this.isSelected());
newWrapper.setShowAssignments(this.isShowAssignments());
newWrapper.setShowInheritedObjectAttributes(this.isShowInheritedObjectAttributes());
newWrapper.setReadonly(this.isReadonly());
}
@Override
public String debugDump() {
return debugDump(0);
}
@Override
public String debugDump(int indent) {
StringBuilder sb = new StringBuilder();
DebugUtil.indentDebugDump(sb, indent);
sb.append("ObjectWrapper(\n");
DebugUtil.debugDumpWithLabel(sb, "displayName", displayName, indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "description", description, indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "object", object == null ? null : object.toString(), indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "objectOld", objectOld == null ? null : objectOld.toString(), indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "oldDelta", oldDelta, indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "status", status == null ? null : status.toString(), indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "headerStatus", headerStatus == null ? null : headerStatus.toString(), indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "containers", containers, indent + 1);
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "loadOptions", loadOptions, indent + 1);
sb.append("\n");
DebugUtil.indentDebugDump(sb, indent);
sb.append(")");
return sb.toString();
}
}