Skip to content

Commit

Permalink
Support new associations
Browse files Browse the repository at this point in the history
1. Added "defaultItemTypeName" annotation to support parsing
associations (items in shadow <associations> container) without
specifying the explicit xsi:type of ShadowAssociationValueType.

2. Added experimental Checkable interface.

3. Un-deprecated Definition#isIgnored method. It is quite useful
as a shortcut.

4. Added some less important auxiliary methods.
  • Loading branch information
mederly committed Feb 26, 2024
1 parent 4c4f042 commit ee722e1
Show file tree
Hide file tree
Showing 23 changed files with 183 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@

package com.evolveum.midpoint.prism;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;
import javax.xml.namespace.QName;

import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -87,6 +84,9 @@ public interface ComplexTypeDefinition extends TypeDefinition, LocalItemDefiniti
@Experimental
boolean isListMarker();

/** Type name for items that are not explicitly defined in this CTD. */
@Nullable QName getDefaultItemTypeName();

/**
* When resolving unqualified names for items contained in this CTD, what should be the default namespace
* to look into at first. Currently does NOT apply recursively (to inner CTDs).
Expand Down Expand Up @@ -182,4 +182,14 @@ default boolean isItemDefinitionRemoved(QName itemName) {
default boolean hasOperationalOnlyItems() {
return false;
}

default List<PrismPropertyDefinition<?>> getPropertyDefinitions() {
List<PrismPropertyDefinition<?>> props = new ArrayList<>();
for (ItemDefinition<?> def : getDefinitions()) {
if (def instanceof PrismPropertyDefinition<?> propertyDefinition) {
props.add(propertyDefinition);
}
}
return props;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,7 @@ public interface Definition
*/
boolean isRuntimeSchema();

/**
* Item definition that has this flag set should be ignored by any processing.
* The ignored item is still part of the schema. Item instances may appear in
* the serialized data formats (e.g. XML) or data store and the parser should
* not raise an error if it encounters them. But any high-level processing code
* should ignore presence of this item. E.g. it should not be displayed to the user,
* should not be present in transformed data structures, etc.
*
* Note that the same item can be ignored at higher layer (e.g. presentation)
* but not ignored at lower layer (e.g. model). This works by presenting different
* item definitions for these layers (see LayerRefinedAttributeDefinition).
*
* Semantics of this flag for complex type definitions is to be defined yet.
*/
// TODO Remove in 4.3, check whether all usages are equivalent to getProcessing() == IGNORE
@Deprecated
/** Just a shortcut for now. */
default boolean isIgnored() {
return getProcessing() == ItemProcessing.IGNORE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,18 @@ default V getAnyValue(@NotNull ValueSelector<V> selector) {
@NotNull
Collection<?> getRealValues();

/**
* Returns detached collection of real values, although the values are still _connected_ to the original item
* (in case of complex properties, references, and containers).
*
* BEWARE, it's not always possible to get the real values.
*/
default <X> @NotNull Collection<X> getRealValues(Class<X> type) {
return getRealValues().stream() // TODO should we avoid using streams here?
.map(type::cast)
.collect(Collectors.toList());
}

@Experimental // Do NOT use !!!!
@NotNull
default Collection<Object> getRealValuesOrRawTypes(PrismContext prismContext) {
Expand Down Expand Up @@ -616,7 +628,7 @@ default void applyDefinitionIfMissing(@NotNull D definition) throws SchemaExcept
*/
void applyDefinition(@NotNull D definition, boolean force) throws SchemaException;

default Item<?,?> copy() {
default Item<V, D> copy() {
return clone();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public interface MutableComplexTypeDefinition extends ComplexTypeDefinition, Mut

void setReferenceMarker(boolean value);

void setDefaultItemTypeName(QName value);

void setDefaultNamespace(String namespace);

void setIgnoredNamespaces(@NotNull List<String> ignoredNamespaces);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public class PrismConstants {
public static final QName A_OBJECT = new QName(NS_ANNOTATION, "object");
public static final QName A_INSTANTIATION_ORDER = new QName(NS_ANNOTATION, "instantiationOrder");

public static final QName A_DEFAULT_ITEM_TYPE_NAME = new QName(NS_ANNOTATION, "defaultItemTypeName");
public static final QName A_DEFAULT_NAMESPACE = new QName(NS_ANNOTATION, "defaultNamespace");
public static final QName A_IGNORED_NAMESPACE = new QName(NS_ANNOTATION, "ignoredNamespace");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public interface PrismProperty<T> extends Item<PrismPropertyValue<T>,PrismProper
* Type override, also for compatibility.
*/

<X> Collection<X> getRealValues(Class<X> type);
<X> @NotNull Collection<X> getRealValues(Class<X> type);

T getAnyRealValue();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ default Collection<?> getRealValuesToReplace() {
return getRealValuesOfCollectionPreservingNull(getValuesToReplace());
}

default Collection<V> getValues(@NotNull ModificationType currentSet) {
return switch (currentSet) {
case ADD -> getValuesToAdd();
case DELETE -> getValuesToDelete();
case REPLACE -> getValuesToReplace();
};
}

/** Values that are added or potentially added by this delta. */
default @NotNull Collection<V> getNewValues() {
var valuesToReplace = getValuesToReplace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import com.evolveum.midpoint.util.annotation.Experimental;
import com.evolveum.midpoint.util.exception.SchemaException;

import static com.evolveum.midpoint.prism.PrismObject.asObjectable;

/**
* Relative difference (delta) of the object.
* This class describes how the object changes. It can describe either object addition, modification of deletion.
Expand Down Expand Up @@ -76,6 +78,10 @@ static boolean isModify(ObjectDelta<?> objectDelta) {

PrismObject<O> getObjectToAdd();

default O getObjectableToAdd() {
return asObjectable(getObjectToAdd());
}

void setObjectToAdd(PrismObject<O> objectToAdd);

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
import com.evolveum.midpoint.prism.match.MatchingRule;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
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 com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

import org.jetbrains.annotations.NotNull;

/**
* TODO clean this up as it is part of prism-api!
*
Expand Down Expand Up @@ -64,19 +65,17 @@ public static boolean isEmpty(PolyStringType value) {
return value == null || StringUtils.isEmpty(value.getOrig()) && StringUtils.isEmpty(value.getNorm());
}

public static <T, X> PrismProperty<X> convertProperty(PrismProperty<T> srcProp, PrismPropertyDefinition<X> targetDef)
throws SchemaException {
if (targetDef.getTypeName().equals(srcProp.getDefinition().getTypeName())) {
// TODO find the correct place for this method
public static <S, T> PrismPropertyValue<T> convertPropertyValue(
@NotNull PrismPropertyValue<S> srcValue, @NotNull PrismPropertyDefinition<T> targetDef) {
Class<T> targetClass = targetDef.getTypeClass();
S srcRealValue = srcValue.getRealValue();
if (targetClass.isInstance(srcRealValue)) {
//noinspection unchecked
return (PrismProperty<X>) srcProp;
return (PrismPropertyValue<T>) srcValue;
} else {
PrismProperty<X> targetProp = targetDef.instantiate();
Class<X> expectedJavaType = XsdTypeMapper.toJavaType(targetDef.getTypeName());
for (PrismPropertyValue<T> srcPVal : srcProp.getValues()) {
X convertedRealValue = JavaTypeConverter.convert(expectedJavaType, srcPVal.getValue());
targetProp.add(PrismContext.get().itemFactory().createPropertyValue(convertedRealValue));
}
return targetProp;
return PrismContext.get().itemFactory().createPropertyValue(
JavaTypeConverter.convert(targetClass, srcRealValue));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

Expand Down Expand Up @@ -64,6 +65,9 @@ public class ComplexTypeDefinitionImpl extends TypeDefinitionImpl implements Mut
/** @see ComplexTypeDefinition#getExtensionForType() */
private QName extensionForType;

/** @see ComplexTypeDefinition#getDefaultItemTypeName() */
private QName defaultItemTypeName;

/** @see ComplexTypeDefinition#getDefaultNamespace() */
private String defaultNamespace;

Expand Down Expand Up @@ -182,6 +186,16 @@ public void setListMarker(boolean listMarker) {
this.listMarker = listMarker;
}

@Override
public @Nullable QName getDefaultItemTypeName() {
return defaultItemTypeName;
}

public void setDefaultItemTypeName(QName defaultItemTypeName) {
checkMutable();
this.defaultItemTypeName = defaultItemTypeName;
}

@Override
public String getDefaultNamespace() {
return defaultNamespace;
Expand Down Expand Up @@ -271,7 +285,11 @@ public <ID extends ItemDefinition<?>> ID findItemDefinition(@NotNull ItemPath pa
Object first = path.first();
if (ItemPath.isName(first)) {
QName firstName = ItemPath.toName(first);
return findNamedItemDefinition(firstName, path.rest(), clazz);
var defFound = findNamedItemDefinition(firstName, path.rest(), clazz);
if (defFound != null) {
return defFound;
}
return tryDefaultItemDefinition(firstName);
} else if (ItemPath.isId(first)) {
path = path.rest();
} else if (ItemPath.isParent(first)) {
Expand Down Expand Up @@ -308,6 +326,36 @@ public <ID extends ItemDefinition<?>> ID findItemDefinition(@NotNull ItemPath pa
}
}

@Override
public <ID extends ItemDefinition<?>> ID findLocalItemDefinition(@NotNull QName name, @NotNull Class<ID> clazz, boolean caseInsensitive) {
var explicit = MutableComplexTypeDefinition.super.findLocalItemDefinition(name, clazz, caseInsensitive);
if (explicit != null) {
return explicit;
}
var defaultItemDef = tryDefaultItemDefinition(name);
//noinspection unchecked
return clazz.isInstance(defaultItemDef) ? (ID) defaultItemDef : null;
}

private <ID extends ItemDefinition<?>> ID tryDefaultItemDefinition(QName firstName) {
var defaultTypeName = getDefaultItemTypeName();
if (defaultTypeName == null) {
return null;
}
var typeDef =
MiscUtil.stateNonNull(
getSchemaRegistry().findComplexTypeDefinitionByType(defaultTypeName),
"No complex type definition for %s", defaultTypeName);
//noinspection unchecked
var pcd = new PrismContainerDefinitionImpl<>(
firstName, typeDef, (Class<? extends Containerable>) typeDef.getCompileTimeClass());
pcd.setMinOccurs(0);
pcd.setMaxOccurs(-1);
pcd.setDynamic(true); // TODO ok?
//noinspection unchecked
return (ID) pcd;
}

private <ID extends ItemDefinition<?>> ID findNamedItemDefinition(
@NotNull QName firstName,
@NotNull ItemPath rest,
Expand Down Expand Up @@ -419,6 +467,7 @@ protected void copyDefinitionDataFrom(ComplexTypeDefinition source) {
objectMarker = source.isObjectMarker();
xsdAnyMarker = source.isXsdAnyMarker();
extensionForType = source.getExtensionForType();
defaultItemTypeName = source.getDefaultItemTypeName();
defaultNamespace = source.getDefaultNamespace();
ignoredNamespaces = new ArrayList<>(source.getIgnoredNamespaces());
itemDefinitions.addAll(source.getDefinitions());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ public String debugFlags() {
StringBuilder sb = new StringBuilder();
debugFlags(sb);
// This starts with a colon, we do not want it here
if (sb.length() > 0) {
if (!sb.isEmpty()) {
sb.deleteCharAt(0);
}
return sb.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,7 @@ public void checkMutable() {

@Override
public List<PrismPropertyDefinition<?>> getPropertyDefinitions() {
List<PrismPropertyDefinition<?>> props = new ArrayList<>();
for (ItemDefinition<?> def : complexTypeDefinition.getDefinitions()) {
if (def instanceof PrismPropertyDefinition) {
props.add((PrismPropertyDefinition<?>) def);
}
}
return props;
return complexTypeDefinition != null ? complexTypeDefinition.getPropertyDefinitions() : List.of();
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ private Class<C> resolveClass(@Nullable Class<C> requiredClass) {
Class<?> parentClass = parent.getCompileTimeClass();
if (parentClass != null) {
if (requiredClass != null && !requiredClass.isAssignableFrom(parentClass)) {
// mismatch; but this can occur (see ShadowAttributesType vs ShadowIdentifiersType in ShadowAssociationType)
// mismatch; but this can occur (see ShadowAttributesType vs ShadowIdentifiersType in association value)
// but TODO maybe this is only a workaround and the problem is in the schema itself (?)
return requiredClass;
} else {
Expand Down Expand Up @@ -1362,7 +1362,7 @@ public void normalize() {
@Override
public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw, ConsistencyCheckScope scope) {
ItemPath myPath = getPath();
if (getDefinition() == null) {
if (requireDefinitions && getDefinition() == null) {
throw new IllegalStateException("Definition-less container value " + this + " (" + myPath + " in " + rootItem + ")");
}
for (Item<?, ?> item : items.values()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public <X> List<PrismPropertyValue<X>> getValues(Class<X> type) {
*/

@Override
public <X> Collection<X> getRealValues(Class<X> type) {
public <X> @NotNull Collection<X> getRealValues(Class<X> type) {
Collection<X> realValues = new ArrayList<>(getValues().size());
for (PrismPropertyValue<T> pValue : getValues()) {
realValues.add((X) pValue.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void addIgnoringEquivalents(@NotNull PrismPropertyValue<T> newValue) thro
delegate().addIgnoringEquivalents(newValue);
}

public <X> Collection<X> getRealValues(Class<X> type) {
public <X> @NotNull Collection<X> getRealValues(Class<X> type) {
return delegate().getRealValues(type);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
Expand Down Expand Up @@ -882,16 +883,21 @@ private ItemDefinition<?> createDynamicItemDefinition(QName itemName, XNode node
return null;
}

PrismPropertyDefinitionImpl<?> propDef = new PrismPropertyDefinitionImpl<>(itemName, typeName);
Integer maxOccurs = node.getMaxOccurs();
if (maxOccurs != null) {
propDef.setMaxOccurs(maxOccurs);
ItemDefinitionImpl<?> itemDef;
var typeDefinition = schemaRegistry.findTypeDefinitionByType(typeName);
if (typeDefinition instanceof ComplexTypeDefinition ctd && (ctd.isContainerMarker() || ctd.isObjectMarker())) {
itemDef = new PrismContainerDefinitionImpl<>(itemName, ctd);
} else if (typeDefinition instanceof ComplexTypeDefinition ctd && ctd.isReferenceMarker()) {
itemDef = new PrismReferenceDefinitionImpl(itemName, typeName);
} else {
// Make this multivalue by default, this is more "open"
propDef.setMaxOccurs(-1);
// definition is unknown or it's a property definition
itemDef = new PrismPropertyDefinitionImpl<>(itemName, typeName);
}
propDef.setDynamic(true);
return propDef;

// Make this multivalue by default, this is more "open"
itemDef.setMaxOccurs(Objects.requireNonNullElse(node.getMaxOccurs(), -1));
itemDef.setDynamic(true);
return itemDef;
}

//endregion
Expand Down

0 comments on commit ee722e1

Please sign in to comment.