/* * 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 com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.path.IdItemPathSegment; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.path.ObjectReferencePathSegment; import com.evolveum.midpoint.prism.path.ParentPathSegment; import com.evolveum.midpoint.prism.schema.PrismSchema; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import javax.xml.namespace.QName; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.*; /** * Definition of a property container. * <p/> * Property container groups properties into logical blocks. The reason for * grouping may be as simple as better understandability of data structure. But * the group usually means different meaning, source or structure of the data. * For example, the property container is frequently used to hold properties * that are dynamic, not fixed by a static schema. Such grouping also naturally * translates to XML and helps to "quarantine" such properties to avoid Unique * Particle Attribute problems. * <p/> * Property Container contains a set of (potentially multi-valued) properties. * The order of properties is not significant, regardless of the fact that it * may be fixed in the XML representation. In the XML representation, each * element inside Property Container must be either Property or a Property * Container. * <p/> * This class represents schema definition for property container. See * {@link Definition} for more details. * * @author Radovan Semancik */ public class PrismContainerDefinitionImpl<C extends Containerable> extends ItemDefinitionImpl<PrismContainer<C>> implements PrismContainerDefinition<C> { private static final long serialVersionUID = -5068923696147960699L; // There are situations where CTD is (maybe) null but class is defined. // TODO clean up this. protected ComplexTypeDefinition complexTypeDefinition; protected Class<C> compileTimeClass; /** * The constructors should be used only occasionally (if used at all). * Use the factory methods in the ResourceObjectDefintion instead. */ public PrismContainerDefinitionImpl(@NotNull QName name, ComplexTypeDefinition complexTypeDefinition, @NotNull PrismContext prismContext) { this(name, complexTypeDefinition, prismContext, null); } public PrismContainerDefinitionImpl(@NotNull QName name, ComplexTypeDefinition complexTypeDefinition, @NotNull PrismContext prismContext, Class<C> compileTimeClass) { super(name, determineTypeName(complexTypeDefinition), prismContext); this.complexTypeDefinition = complexTypeDefinition; if (complexTypeDefinition == null) { isRuntimeSchema = true; super.setDynamic(true); } else { isRuntimeSchema = complexTypeDefinition.isXsdAnyMarker(); super.setDynamic(isRuntimeSchema); } this.compileTimeClass = compileTimeClass; } private static QName determineTypeName(ComplexTypeDefinition complexTypeDefinition) { if (complexTypeDefinition == null) { // Property container without type: xsd:any // FIXME: this is kind of hack, but it works now return DOMUtil.XSD_ANY; } return complexTypeDefinition.getTypeName(); } @Override public Class<C> getCompileTimeClass() { if (compileTimeClass != null) { return compileTimeClass; } if (complexTypeDefinition == null) { return null; } return (Class<C>) complexTypeDefinition.getCompileTimeClass(); } public void setCompileTimeClass(Class<C> compileTimeClass) { this.compileTimeClass = compileTimeClass; } protected String getSchemaNamespace() { return getName().getNamespaceURI(); } @Override public ComplexTypeDefinition getComplexTypeDefinition() { return complexTypeDefinition; } public void setComplexTypeDefinition(ComplexTypeDefinition complexTypeDefinition) { this.complexTypeDefinition = complexTypeDefinition; } @Override public boolean isAbstract() { if (super.isAbstract()) { return true; } return complexTypeDefinition != null && complexTypeDefinition.isAbstract(); } @Override public void revive(PrismContext prismContext) { if (this.prismContext != null) { return; } this.prismContext = prismContext; if (complexTypeDefinition != null) { complexTypeDefinition.revive(prismContext); } } @Override public <D extends ItemDefinition> D findItemDefinition(@NotNull QName name, @NotNull Class<D> clazz, boolean caseInsensitive) { if (complexTypeDefinition != null) { return complexTypeDefinition.findItemDefinition(name, clazz, caseInsensitive); } else { return null; // xsd:any and similar dynamic definitions } } @Override public String getDefaultNamespace() { return complexTypeDefinition != null ? complexTypeDefinition.getDefaultNamespace() : null; } @Override public List<String> getIgnoredNamespaces() { return complexTypeDefinition != null ? complexTypeDefinition.getIgnoredNamespaces() : null; } public <ID extends ItemDefinition> ID findItemDefinition(@NotNull ItemPath path, @NotNull Class<ID> clazz) { for (;;) { if (path.isEmpty()) { if (clazz.isAssignableFrom(PrismContainerDefinition.class)) { return (ID) this; } else { return null; } } ItemPathSegment first = path.first(); if (first instanceof NameItemPathSegment) { QName firstName = ((NameItemPathSegment)first).getName(); return findNamedItemDefinition(firstName, path.rest(), clazz); } else if (first instanceof IdItemPathSegment) { path = path.rest(); } else if (first instanceof ParentPathSegment) { ItemPath rest = path.rest(); ComplexTypeDefinition parent = getSchemaRegistry().determineParentDefinition(getComplexTypeDefinition(), rest); if (rest.isEmpty()) { // requires that the parent is defined as an item (container, object) return (ID) getSchemaRegistry().findItemDefinitionByType(parent.getTypeName()); } else { return parent.findItemDefinition(rest, clazz); } } else if (first instanceof ObjectReferencePathSegment) { throw new IllegalStateException("Couldn't use '@' path segment in this context. PCD=" + getTypeName() + ", path=" + path); } else { throw new IllegalStateException("Unexpected path segment: " + first + " in " + path); } } } @Override public <ID extends ItemDefinition> ID findNamedItemDefinition(@NotNull QName firstName, @NotNull ItemPath rest, @NotNull Class<ID> clazz) { // we need to be compatible with older versions..soo if the path does // not contains qnames with namespaces defined (but the prefix was // specified) match definition according to the local name if (StringUtils.isEmpty(firstName.getNamespaceURI())) { for (ItemDefinition def : getDefinitions()){ if (QNameUtil.match(firstName, def.getName())){ return (ID) def.findItemDefinition(rest, clazz); } } } for (ItemDefinition def : getDefinitions()) { if (firstName.equals(def.getName())) { return (ID) def.findItemDefinition(rest, clazz); } } // if (isRuntimeSchema()) { // return findRuntimeItemDefinition(firstName, rest, clazz); // } return null; } // @Override // public <T> PrismPropertyDefinition<T> findPropertyDefinition(ItemPath path) { // while (!path.isEmpty() && !(path.first() instanceof NameItemPathSegment)) { // path = path.rest(); // } // if (path.isEmpty()) { // throw new IllegalArgumentException("Property path is empty while searching for property definition in " + this); // } // QName firstName = ((NameItemPathSegment)path.first()).getName(); // if (path.size() == 1) { // return findPropertyDefinition(firstName); // } // PrismContainerDefinition pcd = findContainerDefinition(firstName); // if (pcd == null) { // throw new IllegalArgumentException("There is no " + firstName + " subcontainer in " + this); // } // return pcd.findPropertyDefinition(path.rest()); // } /** * Returns set of property definitions. * <p/> * WARNING: This may return definitions from the associated complex type. * Therefore changing the returned set may influence also the complex type definition. * <p/> * The set contains all property definitions of all types that were parsed. * Order of definitions is insignificant. * * @return set of definitions */ @Override public List<? extends ItemDefinition> getDefinitions() { if (complexTypeDefinition == null) { // e.g. for xsd:any containers // FIXME return new ArrayList<>(); } return complexTypeDefinition.getDefinitions(); } /** * Returns set of property definitions. * <p/> * The set contains all property definitions of all types that were parsed. * Order of definitions is insignificant. * <p/> * The returned set is immutable! All changes may be lost. * * @return set of definitions */ @Override public List<PrismPropertyDefinition> getPropertyDefinitions() { List<PrismPropertyDefinition> props = new ArrayList<PrismPropertyDefinition>(); for (ItemDefinition def : complexTypeDefinition.getDefinitions()) { if (def instanceof PrismPropertyDefinition) { props.add((PrismPropertyDefinition) def); } } return props; } @NotNull @Override public PrismContainer<C> instantiate() throws SchemaException { return instantiate(getName()); } @NotNull @Override public PrismContainer<C> instantiate(QName elementName) throws SchemaException { if (isAbstract()) { throw new SchemaException("Cannot instantiate abstract definition "+this); } elementName = addNamespaceIfApplicable(elementName); return new PrismContainer<>(elementName, this, prismContext); } @Override public ContainerDelta<C> createEmptyDelta(ItemPath path) { return new ContainerDelta(path, this, prismContext); } /** * Shallow clone */ @NotNull @Override public PrismContainerDefinitionImpl<C> clone() { PrismContainerDefinitionImpl<C> clone = new PrismContainerDefinitionImpl<C>(name, complexTypeDefinition, prismContext, compileTimeClass); copyDefinitionData(clone); return clone; } protected void copyDefinitionData(PrismContainerDefinitionImpl<C> clone) { super.copyDefinitionData(clone); clone.complexTypeDefinition = this.complexTypeDefinition; clone.compileTimeClass = this.compileTimeClass; } @Override public ItemDefinition deepClone(Map<QName,ComplexTypeDefinition> ctdMap) { PrismContainerDefinitionImpl<C> clone = clone(); ComplexTypeDefinition ctd = getComplexTypeDefinition(); if (ctd != null) { ctd = ctd.deepClone(ctdMap); clone.setComplexTypeDefinition(ctd); } return clone; } @Override public PrismContainerDefinition<C> cloneWithReplacedDefinition(QName itemName, ItemDefinition newDefinition) { PrismContainerDefinitionImpl<C> clone = clone(); clone.replaceDefinition(itemName, newDefinition); return clone; } @Override public void replaceDefinition(QName itemName, ItemDefinition newDefinition) { ComplexTypeDefinition originalComplexTypeDefinition = getComplexTypeDefinition(); ComplexTypeDefinition cloneComplexTypeDefinition = originalComplexTypeDefinition.clone(); setComplexTypeDefinition(cloneComplexTypeDefinition); ((ComplexTypeDefinitionImpl) cloneComplexTypeDefinition).replaceDefinition(itemName, newDefinition); } /** * Creates new instance of property definition and adds it to the container. * <p/> * This is the preferred method of creating a new definition. * * @param name name of the property (element name) * @param typeName XSD type of the property * @return created property definition */ public PrismPropertyDefinitionImpl createPropertyDefinition(QName name, QName typeName) { PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(name, typeName, prismContext); addDefinition(propDef); return propDef; } private void addDefinition(ItemDefinition itemDef) { if (complexTypeDefinition == null) { throw new UnsupportedOperationException("Cannot add an item definition because there's no complex type definition"); } else if (!(complexTypeDefinition instanceof ComplexTypeDefinitionImpl)) { throw new UnsupportedOperationException("Cannot add an item definition into complex type definition of type " + complexTypeDefinition.getClass().getName()); } else { ((ComplexTypeDefinitionImpl) complexTypeDefinition).add(itemDef); } } /** * Creates new instance of property definition and adds it to the container. * <p/> * This is the preferred method of creating a new definition. * * @param name name of the property (element name) * @param typeName XSD type of the property * @param minOccurs minimal number of occurrences * @param maxOccurs maximal number of occurrences (-1 means unbounded) * @return created property definition */ public PrismPropertyDefinition createPropertyDefinition(QName name, QName typeName, int minOccurs, int maxOccurs) { PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(name, typeName, prismContext); propDef.setMinOccurs(minOccurs); propDef.setMaxOccurs(maxOccurs); addDefinition(propDef); return propDef; } // Creates reference to other schema // TODO: maybe check if the name is in different namespace // TODO: maybe create entirely new concept of property reference? public PrismPropertyDefinition createPropertyDefinition(QName name) { PrismPropertyDefinition propDef = new PrismPropertyDefinitionImpl(name, null, prismContext); addDefinition(propDef); return propDef; } /** * Creates new instance of property definition and adds it to the container. * <p/> * This is the preferred method of creating a new definition. * * @param localName name of the property (element name) relative to the schema namespace * @param typeName XSD type of the property * @return created property definition */ public PrismPropertyDefinition createPropertyDefinition(String localName, QName typeName) { QName name = new QName(getSchemaNamespace(), localName); return createPropertyDefinition(name, typeName); } /** * Creates new instance of property definition and adds it to the container. * <p/> * This is the preferred method of creating a new definition. * * @param localName name of the property (element name) relative to the schema namespace * @param localTypeName XSD type of the property * @return created property definition */ public PrismPropertyDefinition createPropertyDefinition(String localName, String localTypeName) { QName name = new QName(getSchemaNamespace(), localName); QName typeName = new QName(getSchemaNamespace(), localTypeName); return createPropertyDefinition(name, typeName); } /** * Creates new instance of property definition and adds it to the container. * <p/> * This is the preferred method of creating a new definition. * * @param localName name of the property (element name) relative to the schema namespace * @param localTypeName XSD type of the property * @param minOccurs minimal number of occurrences * @param maxOccurs maximal number of occurrences (-1 means unbounded) * @return created property definition */ public PrismPropertyDefinition createPropertyDefinition(String localName, String localTypeName, int minOccurs, int maxOccurs) { QName name = new QName(getSchemaNamespace(), localName); QName typeName = new QName(getSchemaNamespace(), localTypeName); PrismPropertyDefinitionImpl propertyDefinition = createPropertyDefinition(name, typeName); propertyDefinition.setMinOccurs(minOccurs); propertyDefinition.setMaxOccurs(maxOccurs); return propertyDefinition; } public PrismContainerDefinition createContainerDefinition(QName name, QName typeName) { return createContainerDefinition(name, typeName, 1, 1); } public PrismContainerDefinition createContainerDefinition(QName name, QName typeName, int minOccurs, int maxOccurs) { PrismSchema typeSchema = prismContext.getSchemaRegistry().findSchemaByNamespace(typeName.getNamespaceURI()); if (typeSchema == null) { throw new IllegalArgumentException("Schema for namespace "+typeName.getNamespaceURI()+" is not known in the prism context"); } ComplexTypeDefinition typeDefinition = typeSchema.findComplexTypeDefinition(typeName); if (typeDefinition == null) { throw new IllegalArgumentException("Type "+typeName+" is not known in the schema"); } return createContainerDefinition(name, typeDefinition, minOccurs, maxOccurs); } public PrismContainerDefinition<C> createContainerDefinition(QName name, ComplexTypeDefinition complexTypeDefinition, int minOccurs, int maxOccurs) { PrismContainerDefinitionImpl<C> def = new PrismContainerDefinitionImpl<C>(name, complexTypeDefinition, prismContext); def.setMinOccurs(minOccurs); def.setMaxOccurs(maxOccurs); addDefinition(def); return def; } @Override public PrismContainerValue<C> createValue() { return new PrismContainerValue<>(prismContext); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); sb.append(toString()); if (isRuntimeSchema()) { sb.append(" dynamic"); } for (Definition def : getDefinitions()) { sb.append("\n"); if (def == this) { // Not perfect loop protection, but works for now DebugUtil.indentDebugDump(sb, indent); sb.append("<itself>"); } else { sb.append(def.debugDump(indent + 1)); } } return sb.toString(); } @Override public boolean isEmpty() { return complexTypeDefinition.isEmpty(); } /** * Return a human readable name of this class suitable for logs. */ @Override protected String getDebugDumpClassName() { return "PCD"; } @Override public String getDocClassName() { return "container"; } }