From 3160ce66a6231458802b94839f33416ae271b20c Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 30 Jun 2020 19:24:02 +0200 Subject: [PATCH] Add built-in metadata mappings support Still very preliminary and incomplete implementation. Support for storage/creationTimestamp is provided. Related to MID-6275. --- .../midpoint/prism/ItemProcessing.java | 2 +- .../prism/path/ItemPathCollectionsUtil.java | 11 + .../midpoint/schema/util/MiscSchemaUtil.java | 1124 +++++++++-------- .../xml/ns/public/common/common-core-3.xsd | 15 +- .../mapping/MetadataMappingEvaluator.java | 10 + .../mapping/ValueMetadataComputation.java | 42 +- .../mapping/ValueMetadataProcessingSpec.java | 71 +- .../builtin/BaseBuiltinMetadataMapping.java | 56 + .../builtin/BuiltinMetadataMapping.java | 32 + .../BuiltinMetadataMappingsRegistry.java | 30 + .../CreateTimestampBuiltinMapping.java | 38 + .../model/intest/TestValueMetadata.java | 36 +- .../archetype-creation-metadata-recording.xml | 14 + .../template-creation-metadata-recording.xml | 42 + .../template-metadata.xml | 20 - .../creation-metadata-recording/user-paul.xml | 16 + 16 files changed, 951 insertions(+), 608 deletions(-) create mode 100644 model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BaseBuiltinMetadataMapping.java create mode 100644 model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMapping.java create mode 100644 model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMappingsRegistry.java create mode 100644 model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/CreateTimestampBuiltinMapping.java create mode 100644 model/model-intest/src/test/resources/metadata/creation-metadata-recording/archetype-creation-metadata-recording.xml create mode 100644 model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-creation-metadata-recording.xml delete mode 100644 model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-metadata.xml create mode 100644 model/model-intest/src/test/resources/metadata/creation-metadata-recording/user-paul.xml diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ItemProcessing.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ItemProcessing.java index b3bf5911d94..43551fee0a2 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ItemProcessing.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ItemProcessing.java @@ -12,7 +12,7 @@ */ public enum ItemProcessing { - IGNORE("ignore"), MINIMAL("minimal"), AUTO("auto"); + IGNORE("ignore"), MINIMAL("minimal"), AUTO("auto"), FULL("full"); private final String stringValue; diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java index 2dfe8b6e163..8072fcede65 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java @@ -172,4 +172,15 @@ public static T getFromMap(Map map, UniformItemPath item } return null; } + + // The code is the same as for UniformItemPath. We should perhaps unify these two. + // (Defeating the purpose of UniformItemPath...) + public static T getFromMap(Map map, ItemPath itemPath) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey().equivalent(itemPath)) { + return entry.getValue(); + } + } + return null; + } } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java index b01de88b531..d8338d7946a 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java @@ -1,561 +1,563 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.schema.util; - -import com.evolveum.midpoint.prism.*; -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.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectListType; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PropertyReferenceListType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; -import org.jetbrains.annotations.NotNull; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; -import java.util.*; - -/** - * @author Radovan Semancik - * - */ -public class MiscSchemaUtil { - - private static final Trace LOGGER = TraceManager.getTrace(MiscSchemaUtil.class); - private static final Random RND = new Random(); - - public static ObjectListType toObjectListType(List> list) { - ObjectListType listType = new ObjectListType(); - for (PrismObject o : list) { - listType.getObject().add(o.asObjectable()); - } - return listType; - } - - public static List> toList(Class type, ObjectListType listType) { - List> list = new ArrayList<>(); - for (ObjectType o : listType.getObject()) { - list.add((PrismObject) o.asPrismObject()); - } - return list; - } - - public static List toObjectableList(List> objectList) { - if (objectList == null) { - return null; - } - List objectableList = new ArrayList<>(objectList.size()); - for (PrismObject object: objectList) { - objectableList.add(object.asObjectable()); - } - return objectableList; - } - - public static ImportOptionsType getDefaultImportOptions() { - ImportOptionsType options = new ImportOptionsType(); - options.setOverwrite(false); - options.setValidateStaticSchema(false); - options.setValidateDynamicSchema(false); - options.setEncryptProtectedValues(true); - options.setFetchResourceSchema(false); - options.setSummarizeErrors(true); - options.setSummarizeSucceses(true); - return options; - } - - public static CachingMetadataType generateCachingMetadata() { - CachingMetadataType cmd = new CachingMetadataType(); - XMLGregorianCalendar xmlGregorianCalendarNow = XmlTypeConverter.createXMLGregorianCalendar(System.currentTimeMillis()); - cmd.setRetrievalTimestamp(xmlGregorianCalendarNow); - cmd.setSerialNumber(generateSerialNumber()); - return cmd; - } - - private static String generateSerialNumber() { - return Long.toHexString(RND.nextLong())+"-"+Long.toHexString(RND.nextLong()); - } - - public static boolean isNullOrEmpty(ProtectedStringType ps) { - return (ps == null || ps.isEmpty()); - } - - public static void setPassword(CredentialsType credentials, ProtectedStringType password) { - PasswordType credPass = credentials.getPassword(); - if (credPass == null) { - credPass = new PasswordType(); - credentials.setPassword(credPass); - } - credPass.setValue(password); - } - - public static Collection toCollection(String entry) { - List list = new ArrayList<>(1); - list.add(entry); - return list; - } - - public static Collection itemReferenceListTypeToItemPathList(PropertyReferenceListType resolve, PrismContext prismContext) { - Collection itemPathList = new ArrayList<>(resolve.getProperty().size()); - for (ItemPathType itemXPathElement: resolve.getProperty()) { - itemPathList.add(prismContext.toPath(itemXPathElement)); - } - return itemPathList; - } - - public static SelectorQualifiedGetOptionsType optionsToOptionsType(Collection> options){ - SelectorQualifiedGetOptionsType optionsType = new SelectorQualifiedGetOptionsType(); - List retval = new ArrayList<>(); - for (SelectorOptions option: options){ - retval.add(selectorOptionToSelectorQualifiedGetOptionType(option)); - } - optionsType.getOption().addAll(retval); - return optionsType; - } - - private static SelectorQualifiedGetOptionType selectorOptionToSelectorQualifiedGetOptionType(SelectorOptions selectorOption) { - OptionObjectSelectorType selectorType = selectorToSelectorType(selectorOption.getSelector()); - GetOperationOptionsType getOptionsType = getOptionsToGetOptionsType(selectorOption.getOptions()); - SelectorQualifiedGetOptionType selectorOptionType = new SelectorQualifiedGetOptionType(); - selectorOptionType.setOptions(getOptionsType); - selectorOptionType.setSelector(selectorType); - return selectorOptionType; - } - - private static OptionObjectSelectorType selectorToSelectorType(ObjectSelector selector) { - if (selector == null) { - return null; - } - OptionObjectSelectorType selectorType = new OptionObjectSelectorType(); - selectorType.setPath(new ItemPathType(selector.getPath())); - return selectorType; - } - - private static GetOperationOptionsType getOptionsToGetOptionsType(GetOperationOptions options) { - GetOperationOptionsType optionsType = new GetOperationOptionsType(); - optionsType.setRetrieve(RetrieveOption.toRetrieveOptionType(options.getRetrieve())); - optionsType.setResolve(options.getResolve()); - optionsType.setResolveNames(options.getResolveNames()); - optionsType.setNoFetch(options.getNoFetch()); - optionsType.setRaw(options.getRaw()); - optionsType.setTolerateRawData(options.getTolerateRawData()); - optionsType.setNoDiscovery(options.getDoNotDiscovery()); - // TODO relational value search query (but it might become obsolete) - optionsType.setAllowNotFound(options.getAllowNotFound()); - optionsType.setPointInTimeType(PointInTimeType.toPointInTimeTypeType(options.getPointInTimeType())); - optionsType.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOptionType(options.getDefinitionProcessing())); - optionsType.setStaleness(options.getStaleness()); - optionsType.setDistinct(options.getDistinct()); - return optionsType; - } - - public static List> optionsTypeToOptions( - SelectorQualifiedGetOptionsType objectOptionsType, PrismContext prismContext) { - if (objectOptionsType == null) { - return null; - } - List> retval = new ArrayList<>(); - for (SelectorQualifiedGetOptionType optionType : objectOptionsType.getOption()) { - retval.add(selectorQualifiedGetOptionTypeToSelectorOption(optionType, prismContext)); - } - return retval; - } - - private static SelectorOptions selectorQualifiedGetOptionTypeToSelectorOption( - SelectorQualifiedGetOptionType objectOptionsType, PrismContext prismContext) { - ObjectSelector selector = selectorTypeToSelector(objectOptionsType.getSelector(), prismContext); - GetOperationOptions options = getOptionsTypeToGetOptions(objectOptionsType.getOptions()); - return new SelectorOptions<>(selector, options); - } - - private static GetOperationOptions getOptionsTypeToGetOptions(GetOperationOptionsType optionsType) { - GetOperationOptions options = new GetOperationOptions(); - options.setRetrieve(RetrieveOption.fromRetrieveOptionType(optionsType.getRetrieve())); - options.setResolve(optionsType.isResolve()); - options.setResolveNames(optionsType.isResolveNames()); - options.setNoFetch(optionsType.isNoFetch()); - options.setRaw(optionsType.isRaw()); - options.setTolerateRawData(optionsType.isTolerateRawData()); - options.setDoNotDiscovery(optionsType.isNoDiscovery()); - // TODO relational value search query (but it might become obsolete) - options.setAllowNotFound(optionsType.isAllowNotFound()); - options.setPointInTimeType(PointInTimeType.toPointInTimeType(optionsType.getPointInTimeType())); - options.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOption(optionsType.getDefinitionProcessing())); - options.setStaleness(optionsType.getStaleness()); - options.setDistinct(optionsType.isDistinct()); - return options; - } - - private static ObjectSelector selectorTypeToSelector(OptionObjectSelectorType selectorType, - PrismContext prismContext) { - if (selectorType == null) { - return null; - } - return new ObjectSelector(prismContext.toUniformPath(selectorType.getPath())); - } - - /** - * Convenience method that helps avoid some compiler warnings. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Collection> createCollection(ObjectDelta... deltas) { - return (Collection)MiscUtil.createCollection(deltas); - } - - /** - * Convenience method that helps avoid some compiler warnings. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Collection> createCollection(ItemDelta... deltas) { - return MiscUtil.createCollection(deltas); - } - - public static Collection> cloneObjectDeltaCollection( - Collection> origCollection) { - if (origCollection == null) { - return null; - } - Collection> clonedCollection = new ArrayList<>(origCollection.size()); - for (ObjectDelta origDelta: origCollection) { - clonedCollection.add(origDelta.clone()); - } - return clonedCollection; - } - - public static Collection> cloneObjectDeltaOperationCollection( - Collection> origCollection) { - if (origCollection == null) { - return null; - } - Collection> clonedCollection = new ArrayList<>(origCollection.size()); - for (ObjectDeltaOperation origDelta: origCollection) { - clonedCollection.add(origDelta.clone()); - } - return clonedCollection; - } - - public static ObjectReferenceType createObjectReference(String oid, QName type) { - ObjectReferenceType ref = new ObjectReferenceType(); - ref.setOid(oid); - ref.setType(type); - return ref; - } - - public static ObjectReferenceType createObjectReference(PrismObject object, Class implicitReferenceTargetType) { - ObjectReferenceType ref = new ObjectReferenceType(); - ref.setOid(object.getOid()); - if (implicitReferenceTargetType == null || !implicitReferenceTargetType.equals(object.getCompileTimeClass())) { - ref.setType(ObjectTypes.getObjectType(object.getCompileTimeClass()).getTypeQName()); - } - ref.setTargetName(PolyString.toPolyStringType(object.getName())); - return ref; - } - - - public static boolean equalsIntent(String intent1, String intent2) { - if (intent1 == null) { - intent1 = SchemaConstants.INTENT_DEFAULT; - } - if (intent2 == null) { - intent2 = SchemaConstants.INTENT_DEFAULT; - } - return intent1.equals(intent2); - } - - public static boolean matchesKind(ShadowKindType expectedKind, ShadowKindType actualKind) { - if (expectedKind == null) { - return true; - } - return expectedKind.equals(actualKind); - } - - - public static AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType( - ProjectionPolicyType accountSynchronizationSettings) { - if (accountSynchronizationSettings == null) { - // default - return AssignmentPolicyEnforcementType.RELATIVE; - } - AssignmentPolicyEnforcementType assignmentPolicyEnforcement = accountSynchronizationSettings.getAssignmentPolicyEnforcement(); - if (assignmentPolicyEnforcement == null) { - return AssignmentPolicyEnforcementType.RELATIVE; - } - return assignmentPolicyEnforcement; - } - - public static PrismReferenceValue objectReferenceTypeToReferenceValue(ObjectReferenceType refType, - PrismContext prismContext) { - if (refType == null) { - return null; - } - PrismReferenceValue rval = prismContext.itemFactory().createReferenceValue(); - rval.setOid(refType.getOid()); - rval.setDescription(refType.getDescription()); - rval.setFilter(refType.getFilter()); - rval.setRelation(refType.getRelation()); - rval.setTargetType(refType.getType()); - return rval; - } - - public static PropertyLimitationsType getLimitationsType(List limitationsTypes, LayerType layer) throws SchemaException { - if (limitationsTypes == null) { - return null; - } - PropertyLimitationsType found = null; - for (PropertyLimitationsType limitType: limitationsTypes) { - if (contains(limitType.getLayer(),layer)) { - if (found == null) { - found = limitType; - } else { - throw new SchemaException("Duplicate definition of limitations for layer '"+layer+"'"); - } - } - } - return found; - } - - private static boolean contains(List layers, LayerType layer) { - if (layers == null || layers.isEmpty()) { - return layer == null; - } - return layers.contains(layer); - } - - public static boolean contains(Collection collection, ObjectReferenceType item) { - for (ObjectReferenceType collectionItem: collection) { - if (matches(collectionItem, item)) { - return true; - } - } - return false; - } - - private static boolean matches(ObjectReferenceType a, ObjectReferenceType b) { - if (a == null && b == null) { - return true; - } - if (a == null || b == null) { - return false; - } - return MiscUtil.equals(a.getOid(), b.getOid()); - } - - // Some searches may return duplicate objects. This is an utility method to remove the duplicates. - public static void reduceSearchResult(List> results) { - if (results == null || results.isEmpty()) { - return; - } - Set oidsSeen = new HashSet<>(); - Iterator> iterator = results.iterator(); - while (iterator.hasNext()) { - PrismObject prismObject = iterator.next(); - if (oidsSeen.contains(prismObject.getOid())) { - iterator.remove(); - } else { - oidsSeen.add(prismObject.getOid()); - } - } - } - - /** - * Returns modification time or creation time (if there was no mo - */ - public static XMLGregorianCalendar getChangeTimestamp(MetadataType metadata) { - if (metadata == null) { - return null; - } - XMLGregorianCalendar modifyTimestamp = metadata.getModifyTimestamp(); - if (modifyTimestamp != null) { - return modifyTimestamp; - } else { - return metadata.getCreateTimestamp(); - } - } - - - public static boolean referenceMatches(ObjectReferenceType refPattern, ObjectReferenceType ref, - PrismContext prismContext) { - if (refPattern.getOid() != null && !refPattern.getOid().equals(ref.getOid())) { - return false; - } - if (refPattern.getType() != null && !QNameUtil.match(refPattern.getType(), ref.getType())) { - return false; - } - if (!prismContext.relationMatches(refPattern.getRelation(), ref.getRelation())) { - return false; - } - return true; - } - - /** - * Make quick and reasonably reliable comparison. E.g. compare prism objects only by - * comparing OIDs. This is ideal for cases when the compare is called often and the - * objects are unlikely to change (e.g. user interface selectable beans). - */ - @SuppressWarnings("rawtypes") - public static boolean quickEquals(Object a, Object b) { - if (a == null && b == null) { - return true; - } - if (a == null || b == null) { - return false; - } - if (a instanceof PrismObject) { - if (b instanceof PrismObject) { - // In case both values are objects then compare only OIDs. - // that should be enough. Comparing complete objects may be slow - // (e.g. if the objects have many assignments) - String aOid = ((PrismObject)a).getOid(); - String bOid = ((PrismObject)b).getOid(); - if (aOid != null && bOid != null) { - return aOid.equals(bOid); - } - } else { - return false; - } - } - if (a instanceof ObjectType) { - if (b instanceof ObjectType) { - // In case both values are objects then compare only OIDs. - // that should be enough. Comparing complete objects may be slow - // (e.g. if the objects have many assignments) - String aOid = ((ObjectType)a).getOid(); - String bOid = ((ObjectType)b).getOid(); - if (aOid != null && bOid != null) { - return aOid.equals(bOid); - } - } else { - return false; - } - } - return a.equals(b); - } - - @NotNull - public static InformationType createInformationType(List messages) { - InformationType rv = new InformationType(); - messages.forEach(s -> rv.getPart().add(new InformationPartType().localizableText(s))); - return rv; - } - - public static ItemProcessing toItemProcessing(ItemProcessingType type) { - if (type == null) { - return null; - } - switch (type) { - case IGNORE: - return ItemProcessing.IGNORE; - case MINIMAL: - return ItemProcessing.MINIMAL; - case AUTO: - return ItemProcessing.AUTO; - default: - throw new IllegalArgumentException("Unknown processing "+type); - } - } - - public static boolean canBeAssignedFrom(QName requiredTypeQName, Class providedTypeClass) { - Class requiredTypeClass = ObjectTypes.getObjectTypeFromTypeQName(requiredTypeQName).getClassDefinition(); - return requiredTypeClass.isAssignableFrom(providedTypeClass); - } - - /** - * This is NOT A REAL METHOD. It just returns null. It is here to mark all the places - * where proper handling of expression profiles should be later added. - */ - public static ExpressionProfile getExpressionProfile() { - return null; - } - - public static void mergeDisplay(DisplayType viewDisplay, DisplayType archetypeDisplay) { - if (viewDisplay.getLabel() == null) { - viewDisplay.setLabel(archetypeDisplay.getLabel()); - } - if (viewDisplay.getSingularLabel() == null) { - viewDisplay.setSingularLabel(archetypeDisplay.getSingularLabel()); - } - if (viewDisplay.getPluralLabel() == null) { - viewDisplay.setPluralLabel(archetypeDisplay.getPluralLabel()); - } - IconType archetypeIcon = archetypeDisplay.getIcon(); - if (archetypeIcon != null) { - IconType viewIcon = viewDisplay.getIcon(); - if (viewIcon == null) { - viewIcon = new IconType(); - viewDisplay.setIcon(viewIcon); - } - if (viewIcon.getCssClass() == null) { - viewIcon.setCssClass(archetypeIcon.getCssClass()); - } - if (viewIcon.getColor() == null) { - viewIcon.setColor(archetypeIcon.getColor()); - } - } - } - - /* - the ordering algorithm is: the first level is occupied by - the column which previousColumn == null || "" || notExistingColumnNameValue. - Each next level contains columns which - previousColumn == columnNameFromPreviousLevel - */ - public static List orderCustomColumns(List customColumns){ - if (customColumns == null || customColumns.size() == 0){ - return new ArrayList<>(); - } - List customColumnsList = new ArrayList<>(customColumns); - List previousColumnValues = new ArrayList<>(); - previousColumnValues.add(null); - previousColumnValues.add(""); - - Map columnRefsMap = new HashMap<>(); - for (GuiObjectColumnType column : customColumns){ - columnRefsMap.put(column.getName(), column.getPreviousColumn() == null ? "" : column.getPreviousColumn()); - } - - List temp = new ArrayList<> (); - int index = 0; - while (index < customColumns.size()){ - int sortFrom = index; - for (int i = index; i < customColumnsList.size(); i++){ - GuiObjectColumnType column = customColumnsList.get(i); - if (previousColumnValues.contains(column.getPreviousColumn()) || - !columnRefsMap.containsKey(column.getPreviousColumn())){ - Collections.swap(customColumnsList, index, i); - index++; - temp.add(column.getName()); - } - } - if (temp.size() == 0){ - temp.add(customColumnsList.get(index).getName()); - index++; - } - if (index - sortFrom > 1){ - customColumnsList.subList(sortFrom, index - 1) - .sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName())); - } - previousColumnValues.clear(); - previousColumnValues.addAll(temp); - temp.clear(); - } - return customColumnsList; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.schema.util; + +import com.evolveum.midpoint.prism.*; +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.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectListType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PropertyReferenceListType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import org.jetbrains.annotations.NotNull; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; +import java.util.*; + +/** + * @author Radovan Semancik + * + */ +public class MiscSchemaUtil { + + private static final Trace LOGGER = TraceManager.getTrace(MiscSchemaUtil.class); + private static final Random RND = new Random(); + + public static ObjectListType toObjectListType(List> list) { + ObjectListType listType = new ObjectListType(); + for (PrismObject o : list) { + listType.getObject().add(o.asObjectable()); + } + return listType; + } + + public static List> toList(Class type, ObjectListType listType) { + List> list = new ArrayList<>(); + for (ObjectType o : listType.getObject()) { + list.add((PrismObject) o.asPrismObject()); + } + return list; + } + + public static List toObjectableList(List> objectList) { + if (objectList == null) { + return null; + } + List objectableList = new ArrayList<>(objectList.size()); + for (PrismObject object: objectList) { + objectableList.add(object.asObjectable()); + } + return objectableList; + } + + public static ImportOptionsType getDefaultImportOptions() { + ImportOptionsType options = new ImportOptionsType(); + options.setOverwrite(false); + options.setValidateStaticSchema(false); + options.setValidateDynamicSchema(false); + options.setEncryptProtectedValues(true); + options.setFetchResourceSchema(false); + options.setSummarizeErrors(true); + options.setSummarizeSucceses(true); + return options; + } + + public static CachingMetadataType generateCachingMetadata() { + CachingMetadataType cmd = new CachingMetadataType(); + XMLGregorianCalendar xmlGregorianCalendarNow = XmlTypeConverter.createXMLGregorianCalendar(System.currentTimeMillis()); + cmd.setRetrievalTimestamp(xmlGregorianCalendarNow); + cmd.setSerialNumber(generateSerialNumber()); + return cmd; + } + + private static String generateSerialNumber() { + return Long.toHexString(RND.nextLong())+"-"+Long.toHexString(RND.nextLong()); + } + + public static boolean isNullOrEmpty(ProtectedStringType ps) { + return (ps == null || ps.isEmpty()); + } + + public static void setPassword(CredentialsType credentials, ProtectedStringType password) { + PasswordType credPass = credentials.getPassword(); + if (credPass == null) { + credPass = new PasswordType(); + credentials.setPassword(credPass); + } + credPass.setValue(password); + } + + public static Collection toCollection(String entry) { + List list = new ArrayList<>(1); + list.add(entry); + return list; + } + + public static Collection itemReferenceListTypeToItemPathList(PropertyReferenceListType resolve, PrismContext prismContext) { + Collection itemPathList = new ArrayList<>(resolve.getProperty().size()); + for (ItemPathType itemXPathElement: resolve.getProperty()) { + itemPathList.add(prismContext.toPath(itemXPathElement)); + } + return itemPathList; + } + + public static SelectorQualifiedGetOptionsType optionsToOptionsType(Collection> options){ + SelectorQualifiedGetOptionsType optionsType = new SelectorQualifiedGetOptionsType(); + List retval = new ArrayList<>(); + for (SelectorOptions option: options){ + retval.add(selectorOptionToSelectorQualifiedGetOptionType(option)); + } + optionsType.getOption().addAll(retval); + return optionsType; + } + + private static SelectorQualifiedGetOptionType selectorOptionToSelectorQualifiedGetOptionType(SelectorOptions selectorOption) { + OptionObjectSelectorType selectorType = selectorToSelectorType(selectorOption.getSelector()); + GetOperationOptionsType getOptionsType = getOptionsToGetOptionsType(selectorOption.getOptions()); + SelectorQualifiedGetOptionType selectorOptionType = new SelectorQualifiedGetOptionType(); + selectorOptionType.setOptions(getOptionsType); + selectorOptionType.setSelector(selectorType); + return selectorOptionType; + } + + private static OptionObjectSelectorType selectorToSelectorType(ObjectSelector selector) { + if (selector == null) { + return null; + } + OptionObjectSelectorType selectorType = new OptionObjectSelectorType(); + selectorType.setPath(new ItemPathType(selector.getPath())); + return selectorType; + } + + private static GetOperationOptionsType getOptionsToGetOptionsType(GetOperationOptions options) { + GetOperationOptionsType optionsType = new GetOperationOptionsType(); + optionsType.setRetrieve(RetrieveOption.toRetrieveOptionType(options.getRetrieve())); + optionsType.setResolve(options.getResolve()); + optionsType.setResolveNames(options.getResolveNames()); + optionsType.setNoFetch(options.getNoFetch()); + optionsType.setRaw(options.getRaw()); + optionsType.setTolerateRawData(options.getTolerateRawData()); + optionsType.setNoDiscovery(options.getDoNotDiscovery()); + // TODO relational value search query (but it might become obsolete) + optionsType.setAllowNotFound(options.getAllowNotFound()); + optionsType.setPointInTimeType(PointInTimeType.toPointInTimeTypeType(options.getPointInTimeType())); + optionsType.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOptionType(options.getDefinitionProcessing())); + optionsType.setStaleness(options.getStaleness()); + optionsType.setDistinct(options.getDistinct()); + return optionsType; + } + + public static List> optionsTypeToOptions( + SelectorQualifiedGetOptionsType objectOptionsType, PrismContext prismContext) { + if (objectOptionsType == null) { + return null; + } + List> retval = new ArrayList<>(); + for (SelectorQualifiedGetOptionType optionType : objectOptionsType.getOption()) { + retval.add(selectorQualifiedGetOptionTypeToSelectorOption(optionType, prismContext)); + } + return retval; + } + + private static SelectorOptions selectorQualifiedGetOptionTypeToSelectorOption( + SelectorQualifiedGetOptionType objectOptionsType, PrismContext prismContext) { + ObjectSelector selector = selectorTypeToSelector(objectOptionsType.getSelector(), prismContext); + GetOperationOptions options = getOptionsTypeToGetOptions(objectOptionsType.getOptions()); + return new SelectorOptions<>(selector, options); + } + + private static GetOperationOptions getOptionsTypeToGetOptions(GetOperationOptionsType optionsType) { + GetOperationOptions options = new GetOperationOptions(); + options.setRetrieve(RetrieveOption.fromRetrieveOptionType(optionsType.getRetrieve())); + options.setResolve(optionsType.isResolve()); + options.setResolveNames(optionsType.isResolveNames()); + options.setNoFetch(optionsType.isNoFetch()); + options.setRaw(optionsType.isRaw()); + options.setTolerateRawData(optionsType.isTolerateRawData()); + options.setDoNotDiscovery(optionsType.isNoDiscovery()); + // TODO relational value search query (but it might become obsolete) + options.setAllowNotFound(optionsType.isAllowNotFound()); + options.setPointInTimeType(PointInTimeType.toPointInTimeType(optionsType.getPointInTimeType())); + options.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOption(optionsType.getDefinitionProcessing())); + options.setStaleness(optionsType.getStaleness()); + options.setDistinct(optionsType.isDistinct()); + return options; + } + + private static ObjectSelector selectorTypeToSelector(OptionObjectSelectorType selectorType, + PrismContext prismContext) { + if (selectorType == null) { + return null; + } + return new ObjectSelector(prismContext.toUniformPath(selectorType.getPath())); + } + + /** + * Convenience method that helps avoid some compiler warnings. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Collection> createCollection(ObjectDelta... deltas) { + return (Collection)MiscUtil.createCollection(deltas); + } + + /** + * Convenience method that helps avoid some compiler warnings. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Collection> createCollection(ItemDelta... deltas) { + return MiscUtil.createCollection(deltas); + } + + public static Collection> cloneObjectDeltaCollection( + Collection> origCollection) { + if (origCollection == null) { + return null; + } + Collection> clonedCollection = new ArrayList<>(origCollection.size()); + for (ObjectDelta origDelta: origCollection) { + clonedCollection.add(origDelta.clone()); + } + return clonedCollection; + } + + public static Collection> cloneObjectDeltaOperationCollection( + Collection> origCollection) { + if (origCollection == null) { + return null; + } + Collection> clonedCollection = new ArrayList<>(origCollection.size()); + for (ObjectDeltaOperation origDelta: origCollection) { + clonedCollection.add(origDelta.clone()); + } + return clonedCollection; + } + + public static ObjectReferenceType createObjectReference(String oid, QName type) { + ObjectReferenceType ref = new ObjectReferenceType(); + ref.setOid(oid); + ref.setType(type); + return ref; + } + + public static ObjectReferenceType createObjectReference(PrismObject object, Class implicitReferenceTargetType) { + ObjectReferenceType ref = new ObjectReferenceType(); + ref.setOid(object.getOid()); + if (implicitReferenceTargetType == null || !implicitReferenceTargetType.equals(object.getCompileTimeClass())) { + ref.setType(ObjectTypes.getObjectType(object.getCompileTimeClass()).getTypeQName()); + } + ref.setTargetName(PolyString.toPolyStringType(object.getName())); + return ref; + } + + + public static boolean equalsIntent(String intent1, String intent2) { + if (intent1 == null) { + intent1 = SchemaConstants.INTENT_DEFAULT; + } + if (intent2 == null) { + intent2 = SchemaConstants.INTENT_DEFAULT; + } + return intent1.equals(intent2); + } + + public static boolean matchesKind(ShadowKindType expectedKind, ShadowKindType actualKind) { + if (expectedKind == null) { + return true; + } + return expectedKind.equals(actualKind); + } + + + public static AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType( + ProjectionPolicyType accountSynchronizationSettings) { + if (accountSynchronizationSettings == null) { + // default + return AssignmentPolicyEnforcementType.RELATIVE; + } + AssignmentPolicyEnforcementType assignmentPolicyEnforcement = accountSynchronizationSettings.getAssignmentPolicyEnforcement(); + if (assignmentPolicyEnforcement == null) { + return AssignmentPolicyEnforcementType.RELATIVE; + } + return assignmentPolicyEnforcement; + } + + public static PrismReferenceValue objectReferenceTypeToReferenceValue(ObjectReferenceType refType, + PrismContext prismContext) { + if (refType == null) { + return null; + } + PrismReferenceValue rval = prismContext.itemFactory().createReferenceValue(); + rval.setOid(refType.getOid()); + rval.setDescription(refType.getDescription()); + rval.setFilter(refType.getFilter()); + rval.setRelation(refType.getRelation()); + rval.setTargetType(refType.getType()); + return rval; + } + + public static PropertyLimitationsType getLimitationsType(List limitationsTypes, LayerType layer) throws SchemaException { + if (limitationsTypes == null) { + return null; + } + PropertyLimitationsType found = null; + for (PropertyLimitationsType limitType: limitationsTypes) { + if (contains(limitType.getLayer(),layer)) { + if (found == null) { + found = limitType; + } else { + throw new SchemaException("Duplicate definition of limitations for layer '"+layer+"'"); + } + } + } + return found; + } + + private static boolean contains(List layers, LayerType layer) { + if (layers == null || layers.isEmpty()) { + return layer == null; + } + return layers.contains(layer); + } + + public static boolean contains(Collection collection, ObjectReferenceType item) { + for (ObjectReferenceType collectionItem: collection) { + if (matches(collectionItem, item)) { + return true; + } + } + return false; + } + + private static boolean matches(ObjectReferenceType a, ObjectReferenceType b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + return MiscUtil.equals(a.getOid(), b.getOid()); + } + + // Some searches may return duplicate objects. This is an utility method to remove the duplicates. + public static void reduceSearchResult(List> results) { + if (results == null || results.isEmpty()) { + return; + } + Set oidsSeen = new HashSet<>(); + Iterator> iterator = results.iterator(); + while (iterator.hasNext()) { + PrismObject prismObject = iterator.next(); + if (oidsSeen.contains(prismObject.getOid())) { + iterator.remove(); + } else { + oidsSeen.add(prismObject.getOid()); + } + } + } + + /** + * Returns modification time or creation time (if there was no mo + */ + public static XMLGregorianCalendar getChangeTimestamp(MetadataType metadata) { + if (metadata == null) { + return null; + } + XMLGregorianCalendar modifyTimestamp = metadata.getModifyTimestamp(); + if (modifyTimestamp != null) { + return modifyTimestamp; + } else { + return metadata.getCreateTimestamp(); + } + } + + + public static boolean referenceMatches(ObjectReferenceType refPattern, ObjectReferenceType ref, + PrismContext prismContext) { + if (refPattern.getOid() != null && !refPattern.getOid().equals(ref.getOid())) { + return false; + } + if (refPattern.getType() != null && !QNameUtil.match(refPattern.getType(), ref.getType())) { + return false; + } + if (!prismContext.relationMatches(refPattern.getRelation(), ref.getRelation())) { + return false; + } + return true; + } + + /** + * Make quick and reasonably reliable comparison. E.g. compare prism objects only by + * comparing OIDs. This is ideal for cases when the compare is called often and the + * objects are unlikely to change (e.g. user interface selectable beans). + */ + @SuppressWarnings("rawtypes") + public static boolean quickEquals(Object a, Object b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a instanceof PrismObject) { + if (b instanceof PrismObject) { + // In case both values are objects then compare only OIDs. + // that should be enough. Comparing complete objects may be slow + // (e.g. if the objects have many assignments) + String aOid = ((PrismObject)a).getOid(); + String bOid = ((PrismObject)b).getOid(); + if (aOid != null && bOid != null) { + return aOid.equals(bOid); + } + } else { + return false; + } + } + if (a instanceof ObjectType) { + if (b instanceof ObjectType) { + // In case both values are objects then compare only OIDs. + // that should be enough. Comparing complete objects may be slow + // (e.g. if the objects have many assignments) + String aOid = ((ObjectType)a).getOid(); + String bOid = ((ObjectType)b).getOid(); + if (aOid != null && bOid != null) { + return aOid.equals(bOid); + } + } else { + return false; + } + } + return a.equals(b); + } + + @NotNull + public static InformationType createInformationType(List messages) { + InformationType rv = new InformationType(); + messages.forEach(s -> rv.getPart().add(new InformationPartType().localizableText(s))); + return rv; + } + + public static ItemProcessing toItemProcessing(ItemProcessingType type) { + if (type == null) { + return null; + } + switch (type) { + case IGNORE: + return ItemProcessing.IGNORE; + case MINIMAL: + return ItemProcessing.MINIMAL; + case AUTO: + return ItemProcessing.AUTO; + case FULL: + return ItemProcessing.FULL; + default: + throw new IllegalArgumentException("Unknown processing "+type); + } + } + + public static boolean canBeAssignedFrom(QName requiredTypeQName, Class providedTypeClass) { + Class requiredTypeClass = ObjectTypes.getObjectTypeFromTypeQName(requiredTypeQName).getClassDefinition(); + return requiredTypeClass.isAssignableFrom(providedTypeClass); + } + + /** + * This is NOT A REAL METHOD. It just returns null. It is here to mark all the places + * where proper handling of expression profiles should be later added. + */ + public static ExpressionProfile getExpressionProfile() { + return null; + } + + public static void mergeDisplay(DisplayType viewDisplay, DisplayType archetypeDisplay) { + if (viewDisplay.getLabel() == null) { + viewDisplay.setLabel(archetypeDisplay.getLabel()); + } + if (viewDisplay.getSingularLabel() == null) { + viewDisplay.setSingularLabel(archetypeDisplay.getSingularLabel()); + } + if (viewDisplay.getPluralLabel() == null) { + viewDisplay.setPluralLabel(archetypeDisplay.getPluralLabel()); + } + IconType archetypeIcon = archetypeDisplay.getIcon(); + if (archetypeIcon != null) { + IconType viewIcon = viewDisplay.getIcon(); + if (viewIcon == null) { + viewIcon = new IconType(); + viewDisplay.setIcon(viewIcon); + } + if (viewIcon.getCssClass() == null) { + viewIcon.setCssClass(archetypeIcon.getCssClass()); + } + if (viewIcon.getColor() == null) { + viewIcon.setColor(archetypeIcon.getColor()); + } + } + } + + /* + the ordering algorithm is: the first level is occupied by + the column which previousColumn == null || "" || notExistingColumnNameValue. + Each next level contains columns which + previousColumn == columnNameFromPreviousLevel + */ + public static List orderCustomColumns(List customColumns){ + if (customColumns == null || customColumns.size() == 0){ + return new ArrayList<>(); + } + List customColumnsList = new ArrayList<>(customColumns); + List previousColumnValues = new ArrayList<>(); + previousColumnValues.add(null); + previousColumnValues.add(""); + + Map columnRefsMap = new HashMap<>(); + for (GuiObjectColumnType column : customColumns){ + columnRefsMap.put(column.getName(), column.getPreviousColumn() == null ? "" : column.getPreviousColumn()); + } + + List temp = new ArrayList<> (); + int index = 0; + while (index < customColumns.size()){ + int sortFrom = index; + for (int i = index; i < customColumnsList.size(); i++){ + GuiObjectColumnType column = customColumnsList.get(i); + if (previousColumnValues.contains(column.getPreviousColumn()) || + !columnRefsMap.containsKey(column.getPreviousColumn())){ + Collections.swap(customColumnsList, index, i); + index++; + temp.add(column.getName()); + } + } + if (temp.size() == 0){ + temp.add(customColumnsList.get(index).getName()); + index++; + } + if (index - sortFrom > 1){ + customColumnsList.subList(sortFrom, index - 1) + .sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName())); + } + previousColumnValues.clear(); + previousColumnValues.addAll(temp); + temp.clear(); + } + return customColumnsList; + } +} diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index 0b6a06cdeb3..9681d82111c 100755 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -8370,14 +8370,25 @@ - All usual processing of the item is applied. Or automated presentation, - transformation or any other processing will take place. + All usual processing of the item is applied. + + + + Full processing of the item is applied. For GUI this means that + automated presentation takes place. For metadata the meaning is that + built-in mappings are applied. + + + + + + diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MetadataMappingEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MetadataMappingEvaluator.java index a5576dd2c11..661c8087d5e 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MetadataMappingEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MetadataMappingEvaluator.java @@ -7,11 +7,17 @@ package com.evolveum.midpoint.model.common.mapping; +import com.evolveum.midpoint.model.common.mapping.builtin.BuiltinMetadataMapping; + +import com.evolveum.midpoint.model.common.mapping.builtin.BuiltinMetadataMappingsRegistry; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.evolveum.midpoint.prism.PrismContext; +import java.util.Collection; + /** * Evaluates metadata mappings. */ @@ -20,5 +26,9 @@ public class MetadataMappingEvaluator { @Autowired MappingFactory mappingFactory; @Autowired PrismContext prismContext; + @Autowired BuiltinMetadataMappingsRegistry builtinMetadataMappingsRegistry; + Collection getBuiltinMappings() { + return builtinMetadataMappingsRegistry.getMappings(); + } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataComputation.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataComputation.java index 453b724a21a..d946d2813d1 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataComputation.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataComputation.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.common.mapping; +import com.evolveum.midpoint.model.common.mapping.builtin.BuiltinMetadataMapping; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; @@ -16,17 +17,12 @@ import com.evolveum.midpoint.schema.metadata.MidpointValueMetadataFactory; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataMappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ValueMetadataType; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import javax.xml.namespace.QName; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.Objects; /** @@ -58,17 +54,19 @@ class ValueMetadataComputation { public ValueMetadata execute() throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { - for (MetadataMappingType mappingBean : computer.processingSpec.getMappings()) { - processMapping(mappingBean); - } + processCustomMappings(); + processBuiltinMappings(); return MidpointValueMetadataFactory.createFrom(outputMetadata); } - private void processMapping(MetadataMappingType mappingBean) throws CommunicationException, ObjectNotFoundException, - SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { - MetadataMappingImpl mapping = createMetadataMapping(mappingBean); - mapping.evaluate(computer.dataMapping.getTask(), result); - appendValues(mapping.getOutputPath(), mapping.getOutputTriple()); + private void processCustomMappings() + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + for (MetadataMappingType mappingBean : computer.processingSpec.getMappings()) { + MetadataMappingImpl mapping = createMapping(mappingBean); + mapping.evaluate(computer.dataMapping.getTask(), result); + appendValues(mapping.getOutputPath(), mapping.getOutputTriple()); + } } private void appendValues(ItemPath outputPath, PrismValueDeltaSetTriple outputTriple) throws SchemaException { @@ -79,7 +77,7 @@ private void appendValues(ItemPath outputPath, PrismValueDeltaSetTriple outpu itemDelta.applyTo(outputMetadata); } - private MetadataMappingImpl createMetadataMapping(MetadataMappingType mappingBean) throws SchemaException { + private MetadataMappingImpl createMapping(MetadataMappingType mappingBean) throws SchemaException { MetadataMappingBuilder builder = computer.metadataMappingEvaluator.mappingFactory.createMappingBuilder(mappingBean, "metadata mapping in " + computer.dataMapping.getMappingContextDescription()); createSources(builder, mappingBean); @@ -141,4 +139,16 @@ private ItemPath getSourcePath(VariableBindingDefinitionType sourceDef) { private String getContextDescription() { return computer.getContextDescription(); // TODO indication of value tuple being processed } + + private void processBuiltinMappings() throws SchemaException { + for (BuiltinMetadataMapping mapping : computer.metadataMappingEvaluator.getBuiltinMappings()) { + if (isApplicable(mapping)) { + mapping.apply(valuesTuple, outputMetadata, computer.dataMapping, result); + } + } + } + + private boolean isApplicable(BuiltinMetadataMapping mapping) throws SchemaException { + return computer.processingSpec.isFullProcessing(mapping.getTargetPath()); + } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataProcessingSpec.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataProcessingSpec.java index 3ce6c63a379..11bbb590332 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataProcessingSpec.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/ValueMetadataProcessingSpec.java @@ -7,19 +7,17 @@ package com.evolveum.midpoint.model.common.mapping; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import com.evolveum.midpoint.model.common.util.ObjectTemplateIncludeProcessor; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataItemDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataMappingType; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; @@ -36,6 +34,11 @@ public class ValueMetadataProcessingSpec { @NotNull private final Collection mappings = new ArrayList<>(); @NotNull private final Collection itemDefinitions = new ArrayList<>(); + /** + * Item processing for given metadata items. Lazily evaluated. + */ + private Map itemProcessingMap; + public boolean isEmpty() { return false; } @@ -90,4 +93,58 @@ public Collection getMappings() { public Collection getItemDefinitions() { return itemDefinitions; } + + private void computeItemProcessingMapIfNeeded() throws SchemaException { + if (itemProcessingMap == null) { + computeItemProcessingMap(); + } + } + + private void computeItemProcessingMap() throws SchemaException { + itemProcessingMap = new HashMap<>(); + for (MetadataItemDefinitionType item : itemDefinitions) { + if (item.getRef() == null) { + throw new SchemaException("No 'ref' in item definition: " + item); + } + ItemProcessingType processing = getProcessingOfItem(item); + if (processing != null) { + itemProcessingMap.put(item.getRef().getItemPath(), processing); + } + } + } + + /** + * Extracts processing information from specified item definition. + */ + private ItemProcessingType getProcessingOfItem(MetadataItemDefinitionType item) throws SchemaException { + Set processing = new HashSet<>(); + for (PropertyLimitationsType limitation : item.getLimitations()) { + if (limitation.getLayer().isEmpty() || limitation.getLayer().contains(LayerType.MODEL)) { + if (limitation.getProcessing() != null) { + processing.add(limitation.getProcessing()); + } + } + } + return MiscUtil.extractSingleton(processing, + () -> new SchemaException("Contradicting 'processing' values for " + item + ": " + processing)); + } + + /** + * Looks up processing information for given path. Proceeds from the most specific to most abstract (empty) path. + */ + private ItemProcessingType getProcessing(ItemPath itemPath) throws SchemaException { + computeItemProcessingMapIfNeeded(); + while (!itemPath.isEmpty()) { + ItemProcessingType processing = ItemPathCollectionsUtil.getFromMap(itemProcessingMap, itemPath); + if (processing != null) { + return processing; + } + itemPath = itemPath.allExceptLast(); + } + return null; + } + + boolean isFullProcessing(ItemPath itemPath) throws SchemaException { + return getProcessing(itemPath) == ItemProcessingType.FULL; + } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BaseBuiltinMetadataMapping.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BaseBuiltinMetadataMapping.java new file mode 100644 index 00000000000..92949da3d05 --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BaseBuiltinMetadataMapping.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.common.mapping.builtin; + +import javax.annotation.PostConstruct; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; + +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.util.exception.SchemaException; + +/** + * TODO + */ +abstract class BaseBuiltinMetadataMapping implements BuiltinMetadataMapping { + + @NotNull private final ItemPath targetPath; +// private ItemDefinition targetDefinition; + +// @Autowired private PrismContext prismContext; + @Autowired private BuiltinMetadataMappingsRegistry registry; + + BaseBuiltinMetadataMapping(@NotNull ItemPath targetItem) { + this.targetPath = targetItem; + } + + @PostConstruct + void register() { + registry.registerBuiltinMapping(this); +// targetDefinition = Objects.requireNonNull( +// prismContext.getSchemaRegistry().getValueMetadataDefinition().findItemDefinition(targetPath), +// () -> "No definition for metadata item " + targetPath); + } + + @Override + @NotNull + public ItemPath getTargetPath() { + return targetPath; + } + + void addPropertyRealValue(PrismContainerValue outputMetadata, Object value) throws SchemaException { + if (value != null) { + PrismProperty property = outputMetadata.findOrCreateProperty(targetPath); + //noinspection unchecked + property.addRealValue(value); + } + } +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMapping.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMapping.java new file mode 100644 index 00000000000..089b28c30c6 --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMapping.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.common.mapping.builtin; + +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ValueMetadataType; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * TODO + */ +public interface BuiltinMetadataMapping { + + void apply(List valuesTuple, PrismContainerValue outputMetadata, + MappingImpl dataMapping, OperationResult result) throws SchemaException; + + @NotNull + ItemPath getTargetPath(); +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMappingsRegistry.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMappingsRegistry.java new file mode 100644 index 00000000000..f8a6d04ad9e --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/BuiltinMetadataMappingsRegistry.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.common.mapping.builtin; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; + +/** + * Registry for built-in metadata mappings. + */ +@Component +public class BuiltinMetadataMappingsRegistry { + + private final List mappings = new ArrayList<>(); + + public List getMappings() { + return mappings; + } + + void registerBuiltinMapping(BaseBuiltinMetadataMapping mapping) { + mappings.add(mapping); + } +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/CreateTimestampBuiltinMapping.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/CreateTimestampBuiltinMapping.java new file mode 100644 index 00000000000..a2a0ce14d2f --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/builtin/CreateTimestampBuiltinMapping.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.common.mapping.builtin; + +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.StorageMetadataType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ValueMetadataType; + +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * Mapping that provides storage/createTimestamp. + */ +@Component +public class CreateTimestampBuiltinMapping extends BaseBuiltinMetadataMapping { + + CreateTimestampBuiltinMapping() { + super(ItemPath.create(ValueMetadataType.F_STORAGE, StorageMetadataType.F_CREATE_TIMESTAMP)); + } + + @Override + public void apply(List valuesTuple, PrismContainerValue outputMetadata, + MappingImpl dataMapping, OperationResult result) throws SchemaException { + addPropertyRealValue(outputMetadata, dataMapping.getNow()); + } +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestValueMetadata.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestValueMetadata.java index a5d1e55d547..1facef54f8d 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestValueMetadata.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestValueMetadata.java @@ -56,6 +56,17 @@ public class TestValueMetadata extends AbstractEmptyModelIntegrationTest { SENSITIVITY_PROPAGATION_DIR, "user-jim.xml", "8d162a31-00a8-48dc-b96f-08d3a85ada1d"); //endregion + //region Constants for creation metadata recording scenario + private static final File CREATION_METADATA_RECORDING_DIR = new File(TEST_DIR, "creation-metadata-recording"); + + private static final TestResource ARCHETYPE_CREATION_METADATA_RECORDING = new TestResource<>( + CREATION_METADATA_RECORDING_DIR, "archetype-creation-metadata-recording.xml", "5fb59a01-e5b9-4531-931d-923c94f341aa"); + private static final TestResource TEMPLATE_CREATION_METADATA_RECORDING = new TestResource<>( + CREATION_METADATA_RECORDING_DIR, "template-creation-metadata-recording.xml", "00301846-fe73-476a-83be-6bfe13251b4a"); + private static final TestResource USER_PAUL = new TestResource<>( + CREATION_METADATA_RECORDING_DIR, "user-paul.xml", "7c8e736b-b195-4ca1-bce4-12f86ff1bc71"); + //endregion + private static final String NS_EXT_METADATA = "http://midpoint.evolveum.com/xml/ns/samples/metadata"; private static final ItemName LOA_NAME = new ItemName(NS_EXT_METADATA, "loa"); private static final ItemPath LOA_PATH = ItemPath.create(ObjectType.F_EXTENSION, LOA_NAME); @@ -77,6 +88,9 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti addObject(ORG_EMPLOYEES, initTask, initResult); addObject(ORG_SPECIAL_MEDICAL_SERVICES, initTask, initResult); + addObject(ARCHETYPE_CREATION_METADATA_RECORDING, initTask, initResult); + addObject(TEMPLATE_CREATION_METADATA_RECORDING, initTask, initResult); + addObject(TEMPLATE_REGULAR_USER, initTask, initResult); addObject(USER_ALICE, initTask, initResult); } @@ -239,6 +253,27 @@ public void test060SimpleMetadataMappingPreview() throws Exception { } //endregion + //region Scenario 0: Creation metadata recording + @Test + public void test080AddPaul() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + addObject(USER_PAUL, task, result); + + then(); + assertUserAfter(USER_PAUL.oid) + .display() + .displayXml() + .assertFullName("Paul Morphy") + .valueMetadata(UserType.F_FULL_NAME) + .display() + .assertSize(1); + } + //endregion + //region Scenario 1: Sensitivity propagation @Test public void test100AddJim() throws Exception { @@ -268,7 +303,6 @@ public void test100AddJim() throws Exception { .end(); // TODO for roleMembershipRef (when implemented) } - //endregion @SuppressWarnings("OptionalUsedAsFieldOrParameterType") diff --git a/model/model-intest/src/test/resources/metadata/creation-metadata-recording/archetype-creation-metadata-recording.xml b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/archetype-creation-metadata-recording.xml new file mode 100644 index 00000000000..9ffce7c29b8 --- /dev/null +++ b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/archetype-creation-metadata-recording.xml @@ -0,0 +1,14 @@ + + + + archetype-creation-metadata-recording + + + + diff --git a/model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-creation-metadata-recording.xml b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-creation-metadata-recording.xml new file mode 100644 index 00000000000..63580fd232a --- /dev/null +++ b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-creation-metadata-recording.xml @@ -0,0 +1,42 @@ + + + + template-creation-metadata-recording + + + + givenName + + + familyName + + + + + + fullName + + + + + + storage/createTimestamp + + full + + + + + fullName + + + + diff --git a/model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-metadata.xml b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-metadata.xml deleted file mode 100644 index c6b190af4da..00000000000 --- a/model/model-intest/src/test/resources/metadata/creation-metadata-recording/template-metadata.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - storage/creation - - auto - - - - - fullName - - - diff --git a/model/model-intest/src/test/resources/metadata/creation-metadata-recording/user-paul.xml b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/user-paul.xml new file mode 100644 index 00000000000..88ee725655c --- /dev/null +++ b/model/model-intest/src/test/resources/metadata/creation-metadata-recording/user-paul.xml @@ -0,0 +1,16 @@ + + + + paul + + + + Paul + Morphy +