/* * Copyright (c) 2010-2015 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 java.util.List; import javax.xml.namespace.QName; import com.evolveum.midpoint.prism.delta.ReferenceDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.PrettyPrinter; import org.jetbrains.annotations.NotNull; /** * Object Reference is a property that describes reference to an object. It is * used to represent association between objects. For example reference from * User object to Account objects that belong to the user. The reference is a * simple uni-directional link using an OID as an identifier. * * This type should be used for all object references so the implementations can * detect them and automatically resolve them. * * @author semancik * */ public class PrismReference extends Item<PrismReferenceValue,PrismReferenceDefinition> { private static final long serialVersionUID = 1872343401395762657L; public PrismReference(QName name) { super(name); } PrismReference(QName name, PrismReferenceDefinition definition, PrismContext prismContext) { super(name, definition, prismContext); } /** * {@inheritDoc} */ public PrismReferenceDefinition getDefinition() { return (PrismReferenceDefinition) super.getDefinition(); } public PrismReferenceValue getValue() { // We are not sure about multiplicity if there is no definition or the definition is dynamic if (getDefinition() != null && !getDefinition().isDynamic()) { if (getDefinition().isMultiValue()) { throw new IllegalStateException("Attempt to get single value from property " + elementName + " with multiple values"); } } if (getValues().size() > 1) { throw new IllegalStateException("Attempt to get single value from property " + elementName + " with multiple values"); } if (getValues().isEmpty()) { // Insert first empty value. This simulates empty single-valued reference. It the reference exists // it is clear that it has at least one value (and that value is empty). PrismReferenceValue rval = new PrismReferenceValue(); add(rval); return rval; } return getValues().iterator().next(); } private PrismReferenceValue getValue(String oid) { // We need to tolerate null OIDs here. Because of JAXB. for (PrismReferenceValue val: getValues()) { if (MiscUtil.equals(oid, val.getOid())) { return val; } } return null; } @Override public Referencable getRealValue() { if (getValue() == null) { return null; } return getValue().asReferencable(); } @Override public Collection<Referencable> getRealValues() { if (getValues() == null) { return null; } List<Referencable> realValues = new ArrayList<>(getValues().size()); for (PrismReferenceValue refVal : getValues()) { realValues.add(refVal.asReferencable()); } return realValues; } public boolean add(@NotNull PrismReferenceValue value) { value.setParent(this); return getValues().add(value); } public boolean merge(PrismReferenceValue value) { String newOid = value.getOid(); // We need to tolerate null OIDs here. Because of JAXB. PrismReferenceValue existingValue = getValue(newOid); if (existingValue == null) { return add(value); } // if there is newValue containing object (instead of oid only) and also // old value containing object (instead of oid only) we need to compare // these two object if they are equals..this can avoid of bad resolving // (e.g. creating user and adding two or more accounts at the same time) if (value.getObject() != null && existingValue.getObject() != null && !value.equalsComplex(existingValue, false, false)){ return add(value); } // in the value.getObject() is not null, it it probably only resolving // of refenrence, so only change oid to object if (value.getObject() != null) { existingValue.setObject(value.getObject()); return true; } // in the case, if the existing value and new value are not equal, add // also another reference alhtrough one with the same oid exist. It is // needed for parent org refs, becasue there can exist more than one // reference with the same oid, but they should be different (e.g. user // is member and also manager of the org. unit.) if (!value.equalsComplex(existingValue, false, false)) { return add(value); } if (value.getTargetType() != null) { existingValue.setTargetType(value.getTargetType()); // return true; } // No need to copy OID as OIDs match return true; } public String getOid() { return getValue().getOid(); } public PolyString getTargetName() { return getValue().getTargetName(); } public PrismReferenceValue findValueByOid(String oid) { for (PrismReferenceValue pval: getValues()) { if (oid.equals(pval.getOid())) { return pval; } } return null; } @Override public Object find(ItemPath path) { if (path == null || path.isEmpty()) { return this; } if (!isSingleValue()) { throw new IllegalStateException("Attempt to resolve sub-path '"+path+"' on multi-value reference " + getElementName()); } PrismReferenceValue value = getValue(); return value.find(path); } @Override public <IV extends PrismValue,ID extends ItemDefinition> PartiallyResolvedItem<IV,ID> findPartial(ItemPath path) { if (path == null || path.isEmpty()) { return new PartiallyResolvedItem<IV,ID>((Item<IV,ID>)this, null); } if (!isSingleValue()) { throw new IllegalStateException("Attempt to resolve sub-path '"+path+"' on multi-value reference " + getElementName()); } PrismReferenceValue value = getValue(); return value.findPartial(path); } @Override public ReferenceDelta createDelta() { return new ReferenceDelta(getPath(), getDefinition(), prismContext); } @Override public ReferenceDelta createDelta(ItemPath path) { return new ReferenceDelta(path, getDefinition(), prismContext); } @Override protected void checkDefinition(PrismReferenceDefinition def) { if (!(def instanceof PrismReferenceDefinition)) { throw new IllegalArgumentException("Cannot apply definition "+def+" to reference "+this); } } @Override public PrismReference clone() { PrismReference clone = new PrismReference(getElementName(), getDefinition(), prismContext); copyValues(clone); return clone; } protected void copyValues(PrismReference clone) { super.copyValues(clone); for (PrismReferenceValue value : getValues()) { clone.add(value.clone()); } } @Override public String toString() { return getClass().getSimpleName() + "(" + PrettyPrinter.prettyPrint(getElementName()) + "):" + getValues(); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); PrismReferenceDefinition definition = getDefinition(); boolean multiline = true; if (definition != null) { multiline = definition.isMultiValue() || definition.isComposite(); } if (DebugUtil.isDetailedDebugDump()) { sb.append(getDebugDumpClassName()).append(": "); } sb.append(DebugUtil.formatElementName(getElementName())); sb.append(": "); List<PrismReferenceValue> values = getValues(); if (getValues() == null) { sb.append("null"); } else if (values.isEmpty()) { sb.append("[ ]"); } else { if (definition != null && DebugUtil.isDetailedDebugDump()) { sb.append(" def "); } for (PrismReferenceValue value : values) { if (multiline) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent + 1); } if (DebugUtil.isDetailedDebugDump()) { if (multiline) { sb.append(value.debugDump(indent + 1, true)); } else { sb.append(value.toString()); } } else { sb.append(value.toHumanReadableString()); } } } return sb.toString(); } /** * Return a human readable name of this class suitable for logs. */ @Override protected String getDebugDumpClassName() { return "PR"; } }