/*
* 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.prism;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.util.PrettyPrinter;
import com.evolveum.midpoint.util.QNameUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
/**
* Abstract item definition in the schema.
*
* This is supposed to be a superclass for all item definitions. Items are things
* that can appear in property containers, which generally means only a property
* and property container itself. Therefore this is in fact superclass for those
* two definitions.
*
* The definitions represent data structures of the schema. Therefore instances
* of Java objects from this class represent specific <em>definitions</em> from
* the schema, not specific properties or objects. E.g the definitions does not
* have any value.
*
* To transform definition to a real property or object use the explicit
* instantiate() methods provided in the definition classes. E.g. the
* instantiate() method will create instance of Property using appropriate
* PropertyDefinition.
*
* The convenience methods in Schema are using this abstract class to find
* appropriate definitions easily.
*
* @author Radovan Semancik
*
*/
public abstract class ItemDefinitionImpl<I extends Item> extends DefinitionImpl implements ItemDefinition<I> {
private static final long serialVersionUID = -2643332934312107274L;
@NotNull protected QName name;
private int minOccurs = 1;
private int maxOccurs = 1;
private boolean operational = false;
private boolean dynamic;
private boolean canAdd = true;
private boolean canRead = true;
private boolean canModify = true;
private boolean inherited;
protected QName substitutionHead;
protected boolean heterogeneousListItem;
private PrismReferenceValue valueEnumerationRef;
// TODO: annotations
/**
* The constructors should be used only occasionally (if used at all).
* Use the factory methods in the ResourceObjectDefintion instead.
*
* @param name definition name (element Name)
* @param typeName type name (XSD complex or simple type)
*/
ItemDefinitionImpl(@NotNull QName name, @NotNull QName typeName, @NotNull PrismContext prismContext) {
super(typeName, prismContext);
this.name = name;
}
/**
* Returns name of the defined entity.
*
* The name is a name of the entity instance if it is fixed by the schema.
* E.g. it may be a name of the property in the container that cannot be
* changed.
*
* The name corresponds to the XML element name in the XML representation of
* the schema. It does NOT correspond to a XSD type name.
*
* Name is optional. If name is not set the null value is returned. If name is
* not set the type is "abstract", does not correspond to the element.
*
* @return the name name of the entity or null.
*/
@Override
@NotNull
public QName getName() {
return name;
}
public void setName(@NotNull QName name) {
this.name = name;
}
@Override
public String getNamespace() {
return getName().getNamespaceURI();
}
/**
* Return the number of minimal value occurrences.
*
* @return the minOccurs
*/
@Override
public int getMinOccurs() {
return minOccurs;
}
public void setMinOccurs(int minOccurs) {
this.minOccurs = minOccurs;
}
/**
* Return the number of maximal value occurrences.
* <p/>
* Any negative number means "unbounded".
*
* @return the maxOccurs
*/
@Override
public int getMaxOccurs() {
return maxOccurs;
}
public void setMaxOccurs(int maxOccurs) {
this.maxOccurs = maxOccurs;
}
/**
* Returns true if property is single-valued.
*
* @return true if property is single-valued.
*/
@Override
public boolean isSingleValue() {
int maxOccurs = getMaxOccurs();
return maxOccurs >= 0 && maxOccurs <= 1;
}
/**
* Returns true if property is multi-valued.
*
* @return true if property is multi-valued.
*/
@Override
public boolean isMultiValue() {
int maxOccurs = getMaxOccurs();
return maxOccurs < 0 || maxOccurs > 1;
}
/**
* Returns true if property is mandatory.
*
* @return true if property is mandatory.
*/
@Override
public boolean isMandatory() {
return getMinOccurs() > 0;
}
/**
* Returns true if property is optional.
*
* @return true if property is optional.
*/
@Override
public boolean isOptional() {
return getMinOccurs() == 0;
}
@Override
public boolean isOperational() {
return operational;
}
public void setOperational(boolean operational) {
this.operational = operational;
}
@Override
public boolean isDynamic() {
return dynamic;
}
public void setDynamic(boolean dynamic) {
this.dynamic = dynamic;
}
/**
* Returns true if the property can be read. I.e. if it is returned in objects
* retrieved from "get", "search" and similar operations.
*/
@Override
public boolean canRead() {
return canRead;
}
/**
* Returns true if the item can be modified. I.e. if it can be changed
* during a modification of existing object.
*/
@Override
public boolean canModify() {
return canModify;
}
/**
*
*/
public void setReadOnly() {
canAdd = false;
canRead = true;
canModify = false;
}
public void setCanRead(boolean read) {
this.canRead = read;
}
public void setCanModify(boolean modify) {
this.canModify = modify;
}
public void setCanAdd(boolean add) {
this.canAdd = add;
}
/**
* Returns true if the item can be added. I.e. if it can be present
* in the object when a new object is created.
*/
@Override
public boolean canAdd() {
return canAdd;
}
@Override
public QName getSubstitutionHead() {
return substitutionHead;
}
public void setSubstitutionHead(QName substitutionHead) {
this.substitutionHead = substitutionHead;
}
@Override
public boolean isHeterogeneousListItem() {
return heterogeneousListItem;
}
public void setHeterogeneousListItem(boolean heterogeneousListItem) {
this.heterogeneousListItem = heterogeneousListItem;
}
/**
* Reference to an object that directly or indirectly represents possible values for
* this item. We do not define here what exactly the object has to be. It can be a lookup
* table, script that dynamically produces the values or anything similar.
* The object must produce the values of the correct type for this item otherwise an
* error occurs.
*/
@Override
public PrismReferenceValue getValueEnumerationRef() {
return valueEnumerationRef;
}
public void setValueEnumerationRef(PrismReferenceValue valueEnumerationRef) {
this.valueEnumerationRef = valueEnumerationRef;
}
@Override
public boolean isValidFor(QName elementQName, Class<? extends ItemDefinition> clazz) {
return isValidFor(elementQName, clazz, false);
}
@Override
public boolean isValidFor(@NotNull QName elementQName, @NotNull Class<? extends ItemDefinition> clazz,
boolean caseInsensitive) {
return clazz.isAssignableFrom(this.getClass())
&& QNameUtil.match(elementQName, getName(), caseInsensitive);
}
@Override
public void adoptElementDefinitionFrom(ItemDefinition otherDef) {
if (otherDef == null) {
return;
}
setName(otherDef.getName());
setMinOccurs(otherDef.getMinOccurs());
setMaxOccurs(otherDef.getMaxOccurs());
}
// add namespace from the definition if it's safe to do so
protected QName addNamespaceIfApplicable(QName name) {
if (StringUtils.isEmpty(name.getNamespaceURI())) {
if (QNameUtil.match(name, this.name)) {
return this.name;
}
}
return name;
}
@Override
public <T extends ItemDefinition> T findItemDefinition(@NotNull ItemPath path, @NotNull Class<T> clazz) {
if (path.isEmpty()) {
if (clazz.isAssignableFrom(this.getClass())) {
return (T) this;
} else {
throw new IllegalArgumentException("Looking for definition of class " + clazz + " but found " + this);
}
} else {
throw new IllegalArgumentException("No definition for path " + path + " in " + this);
}
}
@NotNull
@Override
public abstract ItemDefinition clone();
protected void copyDefinitionData(ItemDefinitionImpl<I> clone) {
super.copyDefinitionData(clone);
clone.name = this.name;
clone.minOccurs = this.minOccurs;
clone.maxOccurs = this.maxOccurs;
clone.dynamic = this.dynamic;
clone.canAdd = this.canAdd;
clone.canRead = this.canRead;
clone.canModify = this.canModify;
clone.operational = this.operational;
clone.valueEnumerationRef = this.valueEnumerationRef;
}
/**
* Make a deep clone, cloning all the sub-items and definitions.
*
* @param ultraDeep if set to true then even the objects that were same instance in the original will be
* cloned as separate instances in the clone.
*
*/
@Override
public ItemDefinition<I> deepClone(boolean ultraDeep) {
if (ultraDeep) {
return deepClone(null);
} else {
return deepClone(new HashMap<>());
}
}
@Override
public ItemDefinition<I> deepClone(Map<QName, ComplexTypeDefinition> ctdMap) {
return clone();
}
@Override
public void revive(PrismContext prismContext) {
if (this.prismContext != null) {
return;
}
this.prismContext = prismContext;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + maxOccurs;
result = prime * result + minOccurs;
result = prime * result + (canAdd ? 1231 : 1237);
result = prime * result + (canRead ? 1231 : 1237);
result = prime * result + (canModify ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
ItemDefinitionImpl other = (ItemDefinitionImpl) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (maxOccurs != other.maxOccurs)
return false;
if (minOccurs != other.minOccurs)
return false;
if (canAdd != other.canAdd)
return false;
if (canRead != other.canRead)
return false;
if (canModify != other.canModify)
return false;
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getDebugDumpClassName());
sb.append(":");
sb.append(PrettyPrinter.prettyPrint(getName()));
sb.append(" ");
debugDumpShortToString(sb);
return sb.toString();
}
/**
* Used in debugDumping items. Does not need to have name in it as item already has it. Does not need
* to have class as that is just too much info that is almost anytime pretty obvious anyway.
*/
@Override
public void debugDumpShortToString(StringBuilder sb) {
sb.append(PrettyPrinter.prettyPrint(getTypeName()));
debugMultiplicity(sb);
debugFlags(sb);
}
private void debugMultiplicity(StringBuilder sb) {
sb.append("[");
sb.append(getMinOccurs());
sb.append(",");
sb.append(getMaxOccurs());
sb.append("]");
}
public String debugMultiplicity() {
StringBuilder sb = new StringBuilder();
debugMultiplicity(sb);
return sb.toString();
}
private void debugFlags(StringBuilder sb) {
if (isIgnored()) {
sb.append(",ignored");
}
if (isDynamic()) {
sb.append(",dyn");
}
extendToString(sb);
}
public String debugFlags() {
StringBuilder sb = new StringBuilder();
debugFlags(sb);
// This starts with a collon, we do not want it here
if (sb.length() > 0) {
sb.deleteCharAt(0);
}
return sb.toString();
}
protected void extendToString(StringBuilder sb) {
sb.append(",");
if (canRead()) {
sb.append("R");
} else {
sb.append("-");
}
if (canAdd()) {
sb.append("A");
} else {
sb.append("-");
}
if (canModify()) {
sb.append("M");
} else {
sb.append("-");
}
if (isRuntimeSchema()) {
sb.append(",runtime");
}
if (isOperational()) {
sb.append(",oper");
}
}
@Override
public boolean isInherited() {
return inherited;
}
@Override
public void setInherited(boolean inherited) {
this.inherited = inherited;
}
}