/*
* Copyright (c) 2010-2017 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.prism;
import java.util.ArrayList;
import java.util.Collection;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.prism.delta.ChangeType;
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.polystring.PolyString;
import com.evolveum.midpoint.util.exception.SchemaException;
import org.jetbrains.annotations.NotNull;
/**
* Common supertype for all identity objects. Defines basic properties that each
* object must have to live in our system (identifier, name).
*
* Objects consists of identifier and name (see definition below) and a set of
* properties represented as XML elements in the object's body. The attributes
* are represented as first-level XML elements (tags) of the object XML
* representation and may be also contained in other tags (e.g. extension,
* attributes). The QName (namespace and local name) of the element holding the
* property is considered to be a property name.
*
* This class is named PrismObject instead of Object to avoid confusion with
* java.lang.Object.
*
* @author Radovan Semancik
*
* Class invariant: has at most one value (potentially empty).
* When making this object immutable and there's no value, we create one; in order
* to prevent exceptions on later getValue calls.
*/
public class PrismObject<O extends Objectable> extends PrismContainer<O> {
private static final long serialVersionUID = 7321429132391159949L;
private static final String PROPERTY_NAME_LOCALPART = "name";
public PrismObject(QName name, Class<O> compileTimeClass) {
super(name, compileTimeClass);
}
public PrismObject(QName name, Class<O> compileTimeClass, PrismContext prismContext) {
super(name, compileTimeClass, prismContext);
}
public PrismObject(QName name, PrismObjectDefinition<O> definition, PrismContext prismContext) {
super(name, definition, prismContext);
}
public PrismObjectValue<O> createNewValue() {
checkMutability();
PrismObjectValue<O> newValue = new PrismObjectValue<>(prismContext);
try {
add(newValue, false);
return newValue;
} catch (SchemaException e) {
// This should not happen
throw new SystemException("Internal Error: " + e.getMessage(), e);
}
}
@NotNull
public PrismObjectValue<O> getValue() {
if (values.isEmpty()) {
return createNewValue();
} else if (values.size() > 1) {
throw new IllegalStateException("PrismObject with more than one value: " + values);
}
return (PrismObjectValue<O>) values.get(0);
}
@Override
public void setValue(@NotNull PrismContainerValue<O> value) throws SchemaException {
clear();
add(value, false);
}
@Override
public boolean add(@NotNull PrismContainerValue newValue, boolean checkUniqueness) throws SchemaException {
if (!(newValue instanceof PrismObjectValue)) {
throw new IllegalArgumentException("Couldn't add non-PrismObjectValue to a PrismObject: value = "
+ newValue + ", object = " + this);
}
if (values.size() > 1) {
throw new IllegalStateException("PrismObject with more than one value: " + this);
} else if (values.size() == 1) {
PrismObjectValue<O> value = (PrismObjectValue<O>) values.get(0);
if (value.isEmpty() && value.getOid() == null) {
clear();
} else {
throw new IllegalStateException("PrismObject cannot have more than one value. New value = " + newValue
+ ", object = " + this);
}
}
return super.add(newValue, checkUniqueness);
}
/**
* Returns Object ID (OID).
*
* May return null if the object does not have an OID.
*
* @return Object ID (OID)
*/
public String getOid() {
return getValue().getOid();
}
public void setOid(String oid) {
checkMutability();
getValue().setOid(oid);
}
public String getVersion() {
return getValue().getVersion();
}
public void setVersion(String version) {
checkMutability();
getValue().setVersion(version);
}
@Override
public PrismObjectDefinition<O> getDefinition() {
return (PrismObjectDefinition<O>) super.getDefinition();
}
@NotNull
public O asObjectable() {
return getValue().asObjectable();
}
public PolyString getName() {
PrismProperty<PolyString> nameProperty = getValue().findProperty(getNamePropertyElementName());
if (nameProperty == null) {
return null;
}
return nameProperty.getRealValue();
}
private QName getNamePropertyElementName() {
return new QName(getElementName().getNamespaceURI(), PrismConstants.NAME_LOCAL_NAME);
}
public PrismContainer<?> getExtension() {
return (PrismContainer<?>) getValue().findItem(getExtensionContainerElementName(), PrismContainer.class);
}
public <I extends Item> I findExtensionItem(QName elementName) {
PrismContainer<?> extension = getExtension();
if (extension == null) {
return null;
}
return (I) extension.findItem(elementName);
}
public <I extends Item> void addExtensionItem(I item) throws SchemaException {
PrismContainer<?> extension = getExtension();
if (extension == null) {
extension = createExtension();
}
extension.add(item);
}
public PrismContainer<?> createExtension() throws SchemaException {
PrismObjectDefinition<O> objeDef = getDefinition();
PrismContainerDefinition<Containerable> extensionDef = objeDef.findContainerDefinition(getExtensionContainerElementName());
PrismContainer<?> extensionContainer = extensionDef.instantiate();
getValue().add(extensionContainer);
return extensionContainer;
}
private QName getExtensionContainerElementName() {
return new QName(getElementName().getNamespaceURI(), PrismConstants.EXTENSION_LOCAL_NAME);
}
@Override
public void applyDefinition(PrismContainerDefinition<O> definition) throws SchemaException {
if (!(definition instanceof PrismObjectDefinition)) {
throw new IllegalArgumentException("Cannot apply "+definition+" to object");
}
super.applyDefinition(definition);
}
@Override
public <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> void removeItem(ItemPath path, Class<I> itemType) {
// Objects are only a single-valued containers. The path of the object itself is "empty".
// Fix this special behavior here.
getValue().removeItem(path, itemType);
}
public void addReplaceExisting(Item<?,?> item) throws SchemaException {
getValue().addReplaceExisting(item);
}
@Override
public PrismObject<O> clone() {
if (prismContext != null && prismContext.getMonitor() != null) {
prismContext.getMonitor().beforeObjectClone(this);
}
PrismObject<O> clone = new PrismObject<O>(getElementName(), getDefinition(), prismContext);
copyValues(clone);
if (prismContext != null && prismContext.getMonitor() != null) {
prismContext.getMonitor().afterObjectClone(this, clone);
}
return clone;
}
protected void copyValues(PrismObject<O> clone) {
super.copyValues(clone);
}
public PrismObjectDefinition<O> deepCloneDefinition(boolean ultraDeep) {
return (PrismObjectDefinition<O>) super.deepCloneDefinition(ultraDeep);
}
@NotNull
public ObjectDelta<O> diff(PrismObject<O> other) {
return diff(other, true, false);
}
@NotNull
public ObjectDelta<O> diff(PrismObject<O> other, boolean ignoreMetadata, boolean isLiteral) {
if (other == null) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(getCompileTimeClass(), ChangeType.DELETE, getPrismContext());
objectDelta.setOid(getOid());
return objectDelta;
}
// This must be a modify
ObjectDelta<O> objectDelta = new ObjectDelta<O>(getCompileTimeClass(), ChangeType.MODIFY, getPrismContext());
objectDelta.setOid(getOid());
Collection<? extends ItemDelta> itemDeltas = new ArrayList<>();
diffInternal(other, itemDeltas, ignoreMetadata, isLiteral);
objectDelta.addModifications(itemDeltas);
return objectDelta;
}
public ObjectDelta<O> createDelta(ChangeType changeType) {
ObjectDelta<O> delta = new ObjectDelta<>(getCompileTimeClass(), changeType, getPrismContext());
delta.setOid(getOid());
return delta;
}
public ObjectDelta<O> createAddDelta() {
ObjectDelta<O> delta = createDelta(ChangeType.ADD);
// TODO: clone?
delta.setObjectToAdd(this);
return delta;
}
public ObjectDelta<O> createModifyDelta() {
ObjectDelta<O> delta = createDelta(ChangeType.MODIFY);
delta.setOid(this.getOid());
return delta;
}
public ObjectDelta<O> createDeleteDelta() {
ObjectDelta<O> delta = createDelta(ChangeType.DELETE);
delta.setOid(this.getOid());
return delta;
}
@Override
public void setParent(PrismValue parentValue) {
throw new IllegalStateException("Cannot set parent for an object");
}
@Override
public PrismValue getParent() {
return null;
}
@Override
public ItemPath getPath() {
return ItemPath.EMPTY_PATH;
}
/**
* this method ignores some part of the object during comparison (e.g. source demarcation in values)
* These methods compare the "meaningful" parts of the objects.
*/
public boolean equivalent(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
PrismObject other = (PrismObject) obj;
ObjectDelta<O> delta = diff(other, true, false);
return delta.isEmpty();
}
@Override
public String toString() {
return toDebugName();
}
/**
* Returns short string representing identity of this object.
* It should container object type, OID and name. It should be presented
* in a form suitable for log and diagnostic messages (understandable for
* system administrator).
*/
public String toDebugName() {
return toDebugType()+":"+getOid()+"("+getNamePropertyStringValue()+")";
}
private PrismProperty<PolyString> getNameProperty() {
QName elementName = getElementName();
String myNamespace = elementName.getNamespaceURI();
return findProperty(new QName(myNamespace, PrismConstants.NAME_LOCAL_NAME));
}
private String getNamePropertyStringValue() {
PrismProperty<PolyString> nameProperty = getNameProperty();
if (nameProperty == null) {
return null;
}
PolyString realValue = nameProperty.getRealValue();
if (realValue == null) {
return null;
}
return realValue.getOrig();
}
/**
* Returns short string identification of object type. It should be in a form
* suitable for log messages. There is no requirement for the type name to be unique,
* but it rather has to be compact. E.g. short element names are preferred to long
* QNames or URIs.
* @return
*/
public String toDebugType() {
QName elementName = getElementName();
if (elementName == null) {
return "(unknown)";
}
return elementName.getLocalPart();
}
/**
* Return a human readable name of this class suitable for logs.
*/
@Override
protected String getDebugDumpClassName() {
return "PO";
}
@Override
protected void appendDebugDumpSuffix(StringBuilder sb) {
sb.append("(").append(getOid());
if (getVersion() != null) {
sb.append(", v").append(getVersion());
}
PrismObjectDefinition<O> def = getDefinition();
if (def != null) {
sb.append(", ").append(DebugUtil.formatElementName(def.getTypeName()));
}
sb.append(")");
}
/**
* Return display name intended for business users of midPoint
*/
public String getBusinessDisplayName() {
return getNamePropertyStringValue();
}
@Override
public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw,
ConsistencyCheckScope scope) {
super.checkConsistenceInternal(rootItem, requireDefinitions, prohibitRaw, scope);
if (size() > 1) {
throw new IllegalStateException("PrismObject holding more than one value: " + size() + ": " + this);
}
getValue(); // checks the type by casting to POV
}
@Override
public void setImmutable(boolean immutable) {
if (!this.immutable && immutable && values.isEmpty()) {
createNewValue();
}
super.setImmutable(immutable);
}
public PrismObject<O> cloneIfImmutable() {
return isImmutable() ? clone() : this;
}
public PrismObject<O> createImmutableClone() {
PrismObject<O> clone = clone();
clone.setImmutable(true);
return clone;
}
}