diff --git a/infra/axiom/pom.xml b/infra/axiom/pom.xml new file mode 100644 index 00000000000..8a297e0e81b --- /dev/null +++ b/infra/axiom/pom.xml @@ -0,0 +1,81 @@ + + + + + 4.0.0 + + + infra + com.evolveum.midpoint.infra + 4.2-SNAPSHOT + + + com.evolveum.axiom + axiom + + Axiom + + + true + + + + + org.jetbrains + annotations + + + com.google.guava + guava + + + org.antlr + antlr4-runtime + 4.8-1 + + + + org.testng + testng + test + + + com.evolveum.midpoint.tools + test-ng + test + + + org.assertj + assertj-core + test + + + + + + + + org.antlr + antlr4-maven-plugin + 4.8-1 + + + + antlr4 + + + + + true + true + + + + + diff --git a/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 b/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 new file mode 100644 index 00000000000..bee08241596 --- /dev/null +++ b/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 @@ -0,0 +1,45 @@ +grammar Axiom; + +SEMICOLON : ';'; +LEFT_BRACE : '{'; +RIGHT_BRACE : '}'; +COLON : ':'; +PLUS : '+'; +LINE_COMMENT : [ \n\r\t]* ('//' (~[\r\n]*)) [ \n\r\t]* -> skip; +SEP: [ \n\r\t]+; +IDENTIFIER : [a-zA-Z_/][a-zA-Z0-9_\-./]*; + +fragment SQOUTE : '\''; +fragment DQOUTE : '"'; + + +//fragment SUB_STRING : ('"' (ESC | ~["])*? '"') | ('\'' (ESC | ~['])* '\''); +//fragment ESC : '\\' (["\\/bfnrt] | UNICODE); +//fragment UNICODE : 'u' HEX HEX HEX HEX; +//fragment HEX : [0-9a-fA-F] ; +//STRING: ((~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '"' | '\'' | '}' | '/' | '+')~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '}' )* ) | SUB_STRING ); + +fragment ESC : '\\'; + +STRING_SINGLEQUOTE: SQOUTE ((ESC SQOUTE) | ~[\n'])* SQOUTE; +STRING_DOUBLEQUOTE: DQOUTE ((ESC DQOUTE) | ~[\n"])* DQOUTE; +//STRING_MULTILINE: '"""' (ESC | ~('"""'))* '"""'; + +statement : SEP* identifier SEP* (argument)? SEP* (SEMICOLON | LEFT_BRACE SEP* (statement)* SEP* RIGHT_BRACE SEP*) SEP*; + +identifier : (prefix COLON)? localIdentifier; +prefix : IDENTIFIER; +localIdentifier : IDENTIFIER; + + +// argument : STRING (SEP* PLUS SEP* STRING)* | IDENTIFIER; +argument : identifier | string; +string : singleQuoteString | doubleQuoteString | multilineString; + + +singleQuoteString : STRING_SINGLEQUOTE; +doubleQuoteString : STRING_DOUBLEQUOTE; +multilineString: '"""\n' (~('"""'))*'"""'; + + + diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java b/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java new file mode 100644 index 00000000000..8af5ae6caba --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java @@ -0,0 +1,74 @@ +/* + * 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.axiom.api; + +import java.util.Objects; + +import com.google.common.base.Preconditions; + +public class AxiomIdentifier { + + public static final String AXIOM_NAMESPACE = "https://ns.evolveum.com/axiom"; + private final String namespace; + private final String localName; + + public AxiomIdentifier(String namespace, String localName) { + this.namespace = Preconditions.checkNotNull(namespace, "namespace"); + this.localName = Preconditions.checkNotNull(localName, "localName"); + } + + public String getNamespace() { + return namespace; + } + + public String getLocalName() { + return localName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((localName == null) ? 0 : localName.hashCode()); + result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof AxiomIdentifier)) + return false; + AxiomIdentifier other = (AxiomIdentifier) obj; + if (localName == null) { + if (other.localName != null) + return false; + } else if (!localName.equals(other.localName)) + return false; + if (namespace == null) { + if (other.namespace != null) + return false; + } else if (!namespace.equals(other.namespace)) + return false; + return true; + } + + @Override + public String toString() { + return localName; + } + + public static AxiomIdentifier axiom(String identifier) { + return new AxiomIdentifier(AXIOM_NAMESPACE, identifier); + } + + public static AxiomIdentifier from(String namespace, String localName) { + return new AxiomIdentifier(namespace, localName); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Lazy.java b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Lazy.java new file mode 100644 index 00000000000..4f97b099b4a --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Lazy.java @@ -0,0 +1,45 @@ +/* + * 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.axiom.concepts; + +public class Lazy implements java.util.function.Supplier { + + private static final Lazy NULL = Lazy.instant(null); + private Object value; + + private Lazy(Object supplier) { + value = supplier; + } + + public static final Lazy from(Supplier supplier) { + return new Lazy<>(supplier); + } + + + public static Lazy instant(T value) { + return new Lazy(value); + } + + @SuppressWarnings("unchecked") + public static Lazy nullValue() { + return NULL; + } + + @SuppressWarnings("unchecked") + @Override + public T get() { + if(value instanceof Supplier) { + value = ((Supplier) value).get(); + } + return (T) value; + } + + public interface Supplier extends java.util.function.Supplier { + + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Optionals.java b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Optionals.java new file mode 100644 index 00000000000..192d289544c --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Optionals.java @@ -0,0 +1,16 @@ +package com.evolveum.axiom.concepts; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; + +public class Optionals { + + public static Optionalfirst(Collection collection) { + if(collection.isEmpty()) { + return Optional.empty(); + } + return Optional.ofNullable(collection.iterator().next()); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBaseDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBaseDefinition.java new file mode 100644 index 00000000000..628f31b3ec4 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBaseDefinition.java @@ -0,0 +1,15 @@ +/* + * 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.axiom.lang.api; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomBaseDefinition { + + AxiomIdentifier name(); + String documentation(); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltIn.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltIn.java new file mode 100644 index 00000000000..f76845e020a --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltIn.java @@ -0,0 +1,264 @@ +/* + * 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.axiom.lang.api; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.concepts.Lazy; +import com.evolveum.axiom.concepts.Lazy.Supplier; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableSet; + +public class AxiomBuiltIn { + + public static final Lazy> EMPTY = Lazy.instant(ImmutableMap.of()); + public static final Lazy NO_ARGUMENT = Lazy.nullValue(); + + + private AxiomBuiltIn() { + throw new UnsupportedOperationException("Utility class"); + } + + public static class Item implements AxiomItemDefinition, AxiomStatement { + public static final Item NAME = new Item("name", Type.IDENTIFIER, true); + public static final Item ARGUMENT = new Item("argument", Type.IDENTIFIER, false); + public static final AxiomItemDefinition DOCUMENTATION = new Item("documentation", Type.STRING, true); + public static final AxiomItemDefinition NAMESPACE = new Item("namespace", Type.STRING, true); + public static final AxiomItemDefinition VERSION = new Item("version", Type.STRING, true); + public static final AxiomItemDefinition TYPE_REFERENCE = new Item("type", Type.TYPE_REFERENCE, true); + public static final AxiomItemDefinition TYPE_DEFINITION = new Item("type", Type.TYPE_DEFINITION, false); + public static final AxiomItemDefinition SUPERTYPE_REFERENCE = new Item("extends", Type.TYPE_REFERENCE, false); + public static final Item ROOT_DEFINITION = new Item("root", Type.ROOT_DEFINITION, false); + public static final AxiomItemDefinition ITEM_DEFINITION = new Item("item", Type.ITEM_DEFINITION, false); + public static final Item MODEL_DEFINITION = new Item("model", Type.MODEL, false); + public static final AxiomItemDefinition MIN_OCCURS = new Item("minOccurs", Type.STRING, false); + public static final AxiomItemDefinition MAX_OCCURS = new Item("maxOccurs", Type.STRING, false); + public static final AxiomItemDefinition TARGET_TYPE = new Item("targetType", Type.IDENTIFIER, true); + + public static final AxiomItemDefinition IDENTIFIER_DEFINITION = new Item("identifier", Type.IDENTIFIER_DEFINITION, true); + + public static final AxiomItemDefinition ID_MEMBER = new Item("key", Type.STRING, false); + public static final AxiomItemDefinition ID_SCOPE = new Item("scope", Type.STRING, false); + public static final AxiomItemDefinition ID_SPACE = new Item("space", Type.IDENTIFIER, false); + + + private final AxiomIdentifier identifier; + private final AxiomTypeDefinition type; + private boolean required; + + + private Item(String identifier, AxiomTypeDefinition type, boolean required) { + this.identifier = AxiomIdentifier.axiom(identifier); + this.type = type; + this.required = required; + } + + @Override + public AxiomIdentifier name() { + return identifier; + } + + @Override + public String documentation() { + return ""; + } + + + @Override + public AxiomTypeDefinition type() { + return type; + } + + @Override + public boolean required() { + return required; + } + + @Override + public String toString() { + return AxiomItemDefinition.toString(this); + } + + @Override + public Collection> children() { + return null; + } + + @Override + public Collection> children(AxiomIdentifier name) { + return null; + } + + @Override + public AxiomIdentifier keyword() { + return null; + } + @Override + public AxiomIdentifier value() { + return identifier; + } + } + + public static class Type implements AxiomTypeDefinition { + public static final Type UUID = new Type("uuid"); + public static final Type STRING = new Type("string"); + public static final Type IDENTIFIER = new Type("AxiomIdentifier"); + public static final Type TYPE_REFERENCE = new Type("AxiomTypeReference"); + public static final Type BASE_DEFINITION = + new Type("AxiomBaseDefinition", null, () -> Item.NAME, () -> itemDefs( + Item.NAME, + Item.DOCUMENTATION + )); + + public static final Type MODEL = + new Type("AxiomModel", BASE_DEFINITION, () -> itemDefs( + Item.NAMESPACE, + Item.VERSION, + Item.TYPE_DEFINITION, + Item.ROOT_DEFINITION + )); + + + public static final Type TYPE_DEFINITION = + new Type("AxiomTypeDefinition", BASE_DEFINITION, () -> itemDefs( + Item.ARGUMENT, + Item.IDENTIFIER_DEFINITION, + Item.SUPERTYPE_REFERENCE, + Item.ITEM_DEFINITION + )) { + @Override + public Collection identifiers() { + return TYPE_IDENTIFIER_DEFINITION.get(); + } + }; + + protected static final Lazy> TYPE_IDENTIFIER_DEFINITION = Lazy.from(()-> + ImmutableSet.of(AxiomIdentifierDefinition.global(TYPE_DEFINITION.name(), Item.NAME))); + + + public static final Type ITEM_DEFINITION = + new Type("AxiomItemDefinition", BASE_DEFINITION, () -> itemDefs( + Item.TYPE_REFERENCE, + Item.MIN_OCCURS, + Item.MAX_OCCURS + )) { + + @Override + public Collection identifiers() { + return ITEM_IDENTIFIER_DEFINITION.get(); + } + }; + + public static final Type ROOT_DEFINITION = + new Type("AxiomRootDefinition", ITEM_DEFINITION, () -> itemDefs()) { + + @Override + public Collection identifiers() { + return ROOT_IDENTIFIER_DEFINITION.get(); + } + }; + + protected static final Lazy> ITEM_IDENTIFIER_DEFINITION = Lazy.from(()-> + ImmutableSet.of(AxiomIdentifierDefinition.local(ITEM_DEFINITION.name(), Item.NAME))); + + protected static final Lazy> ROOT_IDENTIFIER_DEFINITION = Lazy.from(()-> + ImmutableSet.of(AxiomIdentifierDefinition.global(AxiomItemDefinition.ROOT_SPACE, Item.NAME))); + + + public static final Type IDENTIFIER_DEFINITION = + new Type("AxiomIdentifierDefinition", BASE_DEFINITION, () -> Item.ID_MEMBER, () -> itemDefs( + Item.ID_MEMBER, + Item.ID_SCOPE, + Item.ID_SPACE + )); + private final AxiomIdentifier identifier; + private final AxiomTypeDefinition superType; + private final Lazy argument; + private final Lazy> items; + + + private Type(String identifier) { + this(identifier, null, Lazy.nullValue(), EMPTY); + } + + private Type(String identifier, Lazy.Supplier> items) { + this(identifier, null, Lazy.nullValue(), Lazy.from(items)); + } + + private Type(String identifier, AxiomTypeDefinition superType, Lazy.Supplier> items) { + this(identifier, superType, NO_ARGUMENT, Lazy.from(items)); + } + + + private Type(String identifier, AxiomTypeDefinition superType, Lazy.Supplier argument, + Lazy.Supplier> items) { + this(identifier, superType, Lazy.from(argument), Lazy.from(items)); + } + + private Type(String identifier, AxiomTypeDefinition superType, Lazy argument, + Lazy> items) { + this.identifier = AxiomIdentifier.axiom(identifier); + this.argument = argument; + this.superType = superType; + this.items = items; + } + + @Override + public AxiomIdentifier name() { + return identifier; + } + + @Override + public String documentation() { + return ""; + } + + @Override + public Optional superType() { + return Optional.ofNullable(superType); + } + + @Override + public Map items() { + return items.get(); + } + + private static Map itemDefs(AxiomItemDefinition... items) { + Builder builder = ImmutableMap.builder(); + + for (AxiomItemDefinition item : items) { + builder.put(item.name(), item); + } + return builder.build(); + } + + @Override + public Collection identifiers() { + return Collections.emptyList(); + } + + @Override + public Optional argument() { + if(argument.get() != null) { + return Optional.of(argument.get()); + } + if(superType != null) { + return superType.argument(); + } + return Optional.empty(); + } + + } + + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomIdentifierDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomIdentifierDefinition.java new file mode 100644 index 00000000000..d1985875020 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomIdentifierDefinition.java @@ -0,0 +1,45 @@ +package com.evolveum.axiom.lang.api; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.google.common.collect.ImmutableSet; + +public interface AxiomIdentifierDefinition { + + Collection components(); + + Scope scope(); + + AxiomIdentifier space(); + + enum Scope { + GLOBAL, + LOCAL + } + + static AxiomIdentifierDefinition global(AxiomIdentifier name, AxiomItemDefinition... components) { + return new AxiomIdentifierDefinitionImpl(ImmutableSet.copyOf(components), name, Scope.GLOBAL); + } + + static AxiomIdentifierDefinition local(AxiomIdentifier name, AxiomItemDefinition... components) { + return new AxiomIdentifierDefinitionImpl(ImmutableSet.copyOf(components), name, Scope.LOCAL); + } + + static Scope scope(String scope) { + if(Scope.GLOBAL.name().equalsIgnoreCase(scope)) { + return Scope.GLOBAL; + } + if(Scope.LOCAL.name().equalsIgnoreCase(scope)) { + return Scope.LOCAL; + } + throw new IllegalArgumentException("Unknown scope " + scope); + } + + static AxiomIdentifierDefinition from(AxiomIdentifier space, Scope scope, Set members) { + return new AxiomIdentifierDefinitionImpl(ImmutableSet.copyOf(members), space, scope); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomIdentifierDefinitionImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomIdentifierDefinitionImpl.java new file mode 100644 index 00000000000..de7b6c75340 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomIdentifierDefinitionImpl.java @@ -0,0 +1,39 @@ +package com.evolveum.axiom.lang.api; + +import java.util.Collection; +import java.util.Set; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.google.common.collect.ImmutableSet; + +class AxiomIdentifierDefinitionImpl implements AxiomIdentifierDefinition { + + private Set components; + + private AxiomIdentifier space; + + private Scope scope; + + public AxiomIdentifierDefinitionImpl(Set components, AxiomIdentifier space, Scope scope) { + super(); + this.components = ImmutableSet.copyOf(components); + this.space = space; + this.scope = scope; + } + + @Override + public Set components() { + return components; + } + + @Override + public Scope scope() { + return scope; + } + + @Override + public AxiomIdentifier space() { + return space; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomItemDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomItemDefinition.java new file mode 100644 index 00000000000..d37cc6cee47 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomItemDefinition.java @@ -0,0 +1,25 @@ +/* + * 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.axiom.lang.api; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.google.common.base.MoreObjects; + +public interface AxiomItemDefinition extends AxiomBaseDefinition { + + AxiomIdentifier ROOT_SPACE = AxiomIdentifier.axiom("AxiomRootDefinition"); + + AxiomTypeDefinition type(); + boolean required(); + + static String toString(AxiomItemDefinition def) { + return MoreObjects.toStringHelper(AxiomItemDefinition.class) + .add("name", def.name()) + .add("type", def.type()) + .toString(); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomModel.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomModel.java new file mode 100644 index 00000000000..be093c92b43 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomModel.java @@ -0,0 +1,5 @@ +package com.evolveum.axiom.lang.api; + +public interface AxiomModel { + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomSchemaContext.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomSchemaContext.java new file mode 100644 index 00000000000..89e8565aa38 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomSchemaContext.java @@ -0,0 +1,17 @@ +package com.evolveum.axiom.lang.api; + +import java.util.Collection; +import java.util.Optional; + +import com.evolveum.axiom.api.AxiomIdentifier; + +public interface AxiomSchemaContext { + + Collection roots(); + + Optional getRoot(AxiomIdentifier type); + + Optional getType(AxiomIdentifier type); + + Collection types(); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java new file mode 100644 index 00000000000..bbaea4573c5 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomTypeDefinition.java @@ -0,0 +1,49 @@ +/* + * 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.axiom.lang.api; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.google.common.collect.ImmutableMap; + +public interface AxiomTypeDefinition extends AxiomBaseDefinition { + + public final AxiomIdentifier IDENTIFIER_MEMBER = AxiomIdentifier.axiom("name"); + public final AxiomIdentifier IDENTIFIER_SPACE = AxiomIdentifier.axiom("AxiomTypeDefinition"); + + Optional argument(); + + Optional superType(); + + Map items(); + + Collection identifiers(); + + default Optional item(AxiomIdentifier child) { + AxiomItemDefinition maybe = items().get(child); + if(maybe != null) { + return Optional.of(maybe); + } + if(superType().isPresent()) { + return superType().get().item(child); + } + return Optional.empty(); + } + + static IdentifierSpaceKey identifier(AxiomIdentifier name) { + return IdentifierSpaceKey.from(ImmutableMap.of(IDENTIFIER_MEMBER, name)); + } + + default Collection requiredItems() { + return items().values().stream().filter(AxiomItemDefinition::required).collect(Collectors.toList()); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/Identifiable.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/Identifiable.java new file mode 100644 index 00000000000..a385213325d --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/Identifiable.java @@ -0,0 +1,31 @@ +package com.evolveum.axiom.lang.api; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; + +public interface Identifiable { + + I identifier(); + + + static > Map identifierMap(Iterable identifiables) { + Builder builder = ImmutableMap.builder(); + putAll(builder, identifiables); + return builder.build(); + } + + static > void putAll(Builder map, Iterable identifiables) { + for (V v : identifiables) { + map.put(v.identifier(), v); + } + } + + static > void putAll(Map map, Iterable identifiables) { + for (V v : identifiables) { + map.put(v.identifier(), v); + } + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/IdentifierSpaceKey.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/IdentifierSpaceKey.java new file mode 100644 index 00000000000..7fe6d301e42 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/IdentifierSpaceKey.java @@ -0,0 +1,59 @@ +package com.evolveum.axiom.lang.api; + +import java.util.Map; +import java.util.Map.Entry; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.google.common.collect.ImmutableMap; + +public class IdentifierSpaceKey { + + private final Map components; + + public IdentifierSpaceKey(Map components) { + this.components = ImmutableMap.copyOf(components); + } + + public Map components() { + return components; + } + + @Override + public int hashCode() { + return components().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(obj == this) { + return true; + } + if(obj instanceof IdentifierSpaceKey) { + return components().equals(((IdentifierSpaceKey) obj).components()); + } + return false; + } + + public static IdentifierSpaceKey from(Map build) { + return new IdentifierSpaceKey(build); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("["); + boolean first = true; + for(Entry val : components().entrySet()) { + if(!first) { + b.append(","); + } + b.append(val.getKey()).append("=").append(val.getValue()); + } + b.append("]"); + return b.toString(); + } + + public static IdentifierSpaceKey of(AxiomIdentifier key, Object value) { + return from(ImmutableMap.of(key, value)); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/AxiomStatement.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/AxiomStatement.java new file mode 100644 index 00000000000..d320f89c71f --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/AxiomStatement.java @@ -0,0 +1,47 @@ +/* + * 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.axiom.lang.api.stmt; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; + +public interface AxiomStatement { + + AxiomIdentifier keyword(); + V value(); + + Collection> children(); + + Collection> children(AxiomIdentifier name); + + default Collection children(AxiomIdentifier name, Class type) { + return children(name).stream().filter(type::isInstance).map(type::cast).collect(Collectors.toList()); + } + + default Optional first(AxiomIdentifier axiomIdentifier, Class class1) { + return children(axiomIdentifier).stream().filter(class1::isInstance).findFirst().map(class1::cast); + } + + default Optional> first(AxiomItemDefinition item) { + return first(item.name()); + } + + default Optional> first(AxiomIdentifier name) { + return children(name).stream().findFirst(); + } + + default Optional firstValue(AxiomIdentifier name, Class type) { + return children(name).stream().filter(s -> type.isInstance(s.value())).map(s -> type.cast(s.value())).findFirst(); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/AxiomStatementStreamListener.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/AxiomStatementStreamListener.java new file mode 100644 index 00000000000..ad2ff098907 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/AxiomStatementStreamListener.java @@ -0,0 +1,21 @@ +/* + * 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.axiom.lang.api.stmt; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.impl.AxiomSyntaxException; + +public interface AxiomStatementStreamListener { + + void endStatement( @Nullable SourceLocation sourceLocation); + void startStatement(@NotNull AxiomIdentifier identifier, @Nullable SourceLocation sourceLocation) throws AxiomSyntaxException; + void argument(@NotNull AxiomIdentifier convert, @Nullable SourceLocation sourceLocation); + void argument(@NotNull String convert, @Nullable SourceLocation sourceLocation); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/SourceLocation.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/SourceLocation.java new file mode 100644 index 00000000000..8164b9f3af4 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/stmt/SourceLocation.java @@ -0,0 +1,41 @@ +package com.evolveum.axiom.lang.api.stmt; + +public class SourceLocation { + + private final String sourceName; + private final int line; + private final int character; + + private SourceLocation(String sourceName, int line, int character) { + this.sourceName = sourceName; + this.line = line; + this.character = character; + } + + public static SourceLocation from(String source, int line, int pos) { + return new SourceLocation(source, line, pos); + } + + + + + @Override + public String toString() { + return sourceName + "["+ line + ":" + character + "]"; + } + + public String getSource() { + return sourceName; + } + + public int getLine() { + return line; + } + + public int getChar() { + return character; + } + + + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AbstractAxiomBaseDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AbstractAxiomBaseDefinition.java new file mode 100644 index 00000000000..9298b9a69e1 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AbstractAxiomBaseDefinition.java @@ -0,0 +1,35 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.List; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBaseDefinition; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.Multimap; + +public class AbstractAxiomBaseDefinition extends AxiomStatementImpl implements AxiomBaseDefinition { + + public static final Factory FACTORY = AbstractAxiomBaseDefinition::new ; + private AxiomIdentifier name; + private String documentation; + + public AbstractAxiomBaseDefinition(AxiomIdentifier keyword, AxiomIdentifier value, List> children, + Multimap> keywordMap) { + super(keyword, value, children, keywordMap); + name = firstValue(AxiomBuiltIn.Item.NAME.name(), AxiomIdentifier.class).get(); + } + + @Override + public AxiomIdentifier name() { + return name; + } + + @Override + public String documentation() { + return documentation; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java new file mode 100644 index 00000000000..fa6dc8b4e99 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomAntlrVisitor.java @@ -0,0 +1,118 @@ +/* + * 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.axiom.lang.impl; + +import java.util.Optional; +import java.util.Set; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.RuleNode; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.antlr.AxiomBaseVisitor; +import com.evolveum.axiom.lang.antlr.AxiomParser.ArgumentContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.IdentifierContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.PrefixContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.StatementContext; +import com.evolveum.axiom.lang.antlr.AxiomParser.StringContext; +import com.evolveum.axiom.lang.api.stmt.AxiomStatementStreamListener; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.google.common.base.Strings; + +public class AxiomAntlrVisitor extends AxiomBaseVisitor { + + private final AxiomIdentifierResolver statements; + private final AxiomStatementStreamListener delegate; + private final Optional> limit; + private final String sourceName; + + public AxiomAntlrVisitor(String name, AxiomIdentifierResolver statements, AxiomStatementStreamListener delegate, + Set limit) { + this.sourceName = name; + this.statements = statements; + this.delegate = delegate; + this.limit = Optional.ofNullable(limit); + } + + private AxiomIdentifier statementIdentifier(IdentifierContext identifier) { + String prefix = nullableText(identifier.prefix()); + String localName = identifier.localIdentifier().getText(); + return statements.resolveStatementIdentifier(prefix, localName); + } + + private String nullableText(ParserRuleContext prefix) { + return prefix != null ? prefix.getText() : null; + } + + @Override + public T visitStatement(StatementContext ctx) { + AxiomIdentifier identifier = statementIdentifier(ctx.identifier()); + if(canEmit(identifier)) { + delegate.startStatement(identifier, sourceLocation(ctx.identifier().start)); + T ret = super.visitStatement(ctx); + delegate.endStatement(sourceLocation(ctx.stop)); + return ret; + } + return defaultResult(); + } + + private boolean canEmit(AxiomIdentifier identifier) { + if (limit.isPresent()) { + return limit.get().contains(identifier); + } + return true; + } + + @Override + public T visitArgument(ArgumentContext ctx) { + if (ctx.identifier() != null) { + delegate.argument(convert(ctx.identifier()), sourceLocation(ctx.start)); + } else { + delegate.argument(convert(ctx.string()), sourceLocation(ctx.start)); + } + return defaultResult(); + } + + private AxiomIdentifier convert(IdentifierContext argument) { + return statementIdentifier(argument); + } + + private String convert(StringContext string) { + if(string.singleQuoteString() != null) { + return convertSingleQuote(string.singleQuoteString().getText()); + } + if(string.doubleQuoteString() != null) { + return covertDoubleQuote(string.doubleQuoteString().getText()); + } + return convertMultiline(string.multilineString().getText()); + } + + private int sourceLine(ParserRuleContext node) { + return node.start.getLine(); + } + + private SourceLocation sourceLocation(Token start) { + return SourceLocation.from(sourceName, start.getLine(), start.getCharPositionInLine()); + } + + private String convertSingleQuote(String text) { + int stop = text.length(); + return text.substring(1, stop - 1); + } + + private String covertDoubleQuote(String text) { + int stop = text.length(); + return text.substring(1, stop - 1); + } + + private String convertMultiline(String text) { + return text.replace("\"\"\"", ""); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java new file mode 100644 index 00000000000..c8357f5d710 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomErrorListener.java @@ -0,0 +1,54 @@ +/* + * 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.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.atn.ATNConfigSet; +import org.antlr.v4.runtime.dfa.DFA; + +public class AxiomErrorListener extends BaseErrorListener { + + private final String source; + private final List exceptions = new ArrayList<>(); + + public AxiomErrorListener(String source) { + this.source = source; + } + + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException e) { + exceptions.add(new AxiomSyntaxException(source, line, charPositionInLine, msg)); + + } + + public void validate() throws AxiomSyntaxException { + if (exceptions.isEmpty()) { + return; + } + final StringBuilder sb = new StringBuilder(); + boolean first = true; + for (AxiomSyntaxException e : exceptions) { + if (first) { + first = false; + } else { + sb.append('\n'); + } + + sb.append(e.getFormattedMessage()); + } + throw new AxiomSyntaxException(source, 0, 0, sb.toString()); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java new file mode 100644 index 00000000000..89650a2cdeb --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomIdentifierResolver.java @@ -0,0 +1,25 @@ +/* + * 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.axiom.lang.impl; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.axiom.api.AxiomIdentifier; + +import org.jetbrains.annotations.Nullable; + +public interface AxiomIdentifierResolver { + + final AxiomIdentifierResolver AXIOM_DEFAULT_NAMESPACE = defaultNamespace(AxiomIdentifier.AXIOM_NAMESPACE); + + AxiomIdentifier resolveStatementIdentifier(@Nullable String prefix, @NotNull String localName); + + static AxiomIdentifierResolver defaultNamespace(String namespace) { + return (prefix, localName) -> prefix == null ? AxiomIdentifier.from(namespace, localName) : null; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomItemDefinitionImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomItemDefinitionImpl.java new file mode 100644 index 00000000000..6e80dd0f8d8 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomItemDefinitionImpl.java @@ -0,0 +1,41 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.List; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.Multimap; + +public class AxiomItemDefinitionImpl extends AbstractAxiomBaseDefinition implements AxiomItemDefinition { + + public static final Factory FACTORY = AxiomItemDefinitionImpl::new ; + private final AxiomTypeDefinition type; + private int minOccurs; + + public AxiomItemDefinitionImpl(AxiomIdentifier keyword, AxiomIdentifier value, List> children, + Multimap> keywordMap) { + super(keyword, value, children, keywordMap); + type = first(AxiomBuiltIn.Item.TYPE_DEFINITION.name(), AxiomTypeDefinition.class) + .orElseThrow(() -> new IllegalStateException("No 'type' declaration in " + super.toString())); + minOccurs = firstValue(AxiomBuiltIn.Item.MIN_OCCURS.name(), String.class).map(Integer::parseInt).orElse(0); + } + + @Override + public AxiomTypeDefinition type() { + return type; + } + + @Override + public boolean required() { + return minOccurs > 0; + } + + @Override + public String toString() { + return AxiomItemDefinition.toString(this); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java new file mode 100644 index 00000000000..80f13954a47 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomModelInfo.java @@ -0,0 +1,14 @@ +/* + * 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.axiom.lang.impl; + +public interface AxiomModelInfo { + + String getModelName(); + String getNamespace(); + String getDescription(); +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSchemaContextImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSchemaContextImpl.java new file mode 100644 index 00000000000..10da0252199 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSchemaContextImpl.java @@ -0,0 +1,63 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomSchemaContext; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +public class AxiomSchemaContextImpl implements AxiomSchemaContext { + + private Map roots; + private Map types; + private Map>> globals; + + public AxiomSchemaContextImpl(Map>> globalMap) { + this.globals = globalMap; + this.roots = Maps.transformValues(globalMap.get(AxiomItemDefinition.ROOT_SPACE), AxiomItemDefinition.class::cast); + this.types = Maps.transformValues(globalMap.get(AxiomTypeDefinition.IDENTIFIER_SPACE), AxiomTypeDefinition.class::cast); + } + + @Override + public Collection roots() { + return roots.values(); + } + + @Override + public Optional getType(AxiomIdentifier type) { + return Optional.ofNullable(types.get(nameKey(type))); + } + + @Override + public Collection types() { + return types.values(); + } + + @Override + public Optional getRoot(AxiomIdentifier type) { + return Optional.ofNullable(roots.get(nameKey(type))); + } + + private static IdentifierSpaceKey nameKey(AxiomIdentifier type) { + return IdentifierSpaceKey.of(AxiomTypeDefinition.IDENTIFIER_MEMBER, type); + } + + public static AxiomSchemaContextImpl boostrapContext() { + Map> root = ImmutableMap.of(nameKey(AxiomBuiltIn.Item.MODEL_DEFINITION.name()), AxiomBuiltIn.Item.MODEL_DEFINITION); + Map>> global + = ImmutableMap.of(AxiomItemDefinition.ROOT_SPACE, root, AxiomTypeDefinition.IDENTIFIER_SPACE, ImmutableMap.of()); + return new AxiomSchemaContextImpl(global); + } + + public static AxiomSchemaContext baseLanguageContext() { + return ModelReactorContext.BASE_LANGUAGE.get(); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSemanticException.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSemanticException.java new file mode 100644 index 00000000000..9c345456e9d --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSemanticException.java @@ -0,0 +1,41 @@ +package com.evolveum.axiom.lang.impl; + +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.google.common.base.Strings; + +public class AxiomSemanticException extends RuntimeException { + + + + public AxiomSemanticException(String message, Throwable cause) { + super(message, cause); + } + + public AxiomSemanticException(String message) { + super(message); + } + + public AxiomSemanticException(Throwable cause) { + super(cause); + } + + public AxiomSemanticException(AxiomItemDefinition definition, String message) { + super(definition.toString() + " " + message); + } + + public static V checkNotNull(V value, AxiomItemDefinition definition, String message) throws AxiomSemanticException { + if(value == null) { + throw new AxiomSemanticException(definition, message); + } + return value; + } + + public static void check(boolean check, SourceLocation start, String format, Object... arguments) { + if(!check) { + throw new AxiomSemanticException(start + Strings.lenientFormat(format, arguments)); + } + ; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementBuilder.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementBuilder.java new file mode 100644 index 00000000000..4d3b2eafebb --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementBuilder.java @@ -0,0 +1,68 @@ +package com.evolveum.axiom.lang.impl; + +import com.evolveum.axiom.concepts.Lazy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.evolveum.axiom.lang.impl.AxiomStatementImpl.Factory; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +public class AxiomStatementBuilder implements Lazy.Supplier> { + + private final AxiomIdentifier identifier; + private final AxiomStatementImpl.Factory> factory; + private T value; + + public AxiomStatementBuilder(AxiomIdentifier identifier, Factory> factory) { + this.identifier = identifier; + this.factory = factory; + } + + public AxiomStatementBuilder(AxiomIdentifier identifier) { + this(identifier, AxiomStatementImpl.factory()); + } + + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + private List>> childList = new ArrayList<>(); + private Multimap>> children = HashMultimap.create(); + + public void add(AxiomItemDefinition item, Supplier> statement) { + add(item.name(), statement); + } + + public void add(AxiomIdentifier type, Supplier> statement) { + childList.add(statement); + children.put(type, statement); + } + + @Override + public AxiomStatement get() { + return factory.create(identifier, value, buildChildList(), buildChildMap()); + } + + private Multimap> buildChildMap() { + return Multimaps.transformValues(children, (v) -> v.get()); + } + + private List> buildChildList() { + return Lists.transform(childList, (v) -> v.get()); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementFactoryContext.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementFactoryContext.java new file mode 100644 index 00000000000..fc3fc6a8341 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementFactoryContext.java @@ -0,0 +1,21 @@ +/* + * 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.axiom.lang.impl; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.impl.AxiomStatementImpl.Factory; + +public interface AxiomStatementFactoryContext { + + AxiomStatementImpl.Factory factoryFor(AxiomTypeDefinition identifier); + + static AxiomStatementFactoryContext defaultFactory(AxiomStatementImpl.Factory factory) { + return (identifier) -> factory; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java new file mode 100644 index 00000000000..9bcf42362ad --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementImpl.java @@ -0,0 +1,84 @@ +/* + * 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.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.ImmutableMap.Builder; + +public class AxiomStatementImpl implements AxiomStatement { + + private final AxiomIdentifier keyword; + private final V value; + private final List> children; + private final Multimap> keywordMap; + + public AxiomStatementImpl(AxiomIdentifier keyword, V value, List> children, + Multimap> keywordMap) { + super(); + this.keyword = keyword; + this.value = value; + this.children = ImmutableList.copyOf(children); + this.keywordMap = ImmutableMultimap.copyOf(keywordMap); + } + + @Override + public Collection> children(AxiomIdentifier type) { + return keywordMap.get(type); + } + + @Override + public Collection> children() { + return children; + } + + @Override + public AxiomIdentifier keyword() { + return keyword; + } + + @Override + public V value() { + return value; + } + + @Override + public String toString() { + return keyword + "{value=" + value + "}"; + } + + interface Factory> { + + I create(AxiomIdentifier type, V value, List> children, + Multimap> keywordMap); + + } + + protected void putAll(Builder builder, + Collection children) { + for (AxiomItemDefinition definition : children) { + builder.put(definition.name(), definition); + } + } + + public static > Factory factory() { + return (Factory) AxiomStatementImpl::new; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java new file mode 100644 index 00000000000..f7c3339dfb1 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementSource.java @@ -0,0 +1,85 @@ +/* + * 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.axiom.lang.impl; + +import java.beans.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; +import java.util.Set; + +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.TokenStream; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.antlr.AxiomLexer; +import com.evolveum.axiom.lang.antlr.AxiomParser; +import com.evolveum.axiom.lang.antlr.AxiomParser.StatementContext; +import com.evolveum.axiom.lang.api.stmt.AxiomStatementStreamListener; + +public class AxiomStatementSource implements AxiomModelInfo { + + private final StatementContext root; + private String sourceName; + + public static AxiomStatementSource from(InputStream stream) throws IOException, AxiomSyntaxException { + return from(null, CharStreams.fromStream(stream)); + } + + public static AxiomStatementSource from(String sourceName, InputStream stream) throws IOException, AxiomSyntaxException { + return from(sourceName, CharStreams.fromStream(stream)); + } + + public static AxiomStatementSource from(String sourceName, CharStream stream) throws AxiomSyntaxException { + + AxiomLexer lexer = new AxiomLexer(stream); + AxiomParser parser = new AxiomParser(new CommonTokenStream(lexer)); + + lexer.removeErrorListeners(); + parser.removeErrorListeners(); + AxiomErrorListener errorListener = new AxiomErrorListener(sourceName); + parser.addErrorListener(errorListener); + StatementContext statement = parser.statement(); + errorListener.validate(); + return new AxiomStatementSource(sourceName, statement); + } + + private AxiomStatementSource(String sourceName, StatementContext statement) { + this.sourceName = sourceName; + this.root = statement; + } + + @Override + public String getModelName() { + return root.argument().identifier().localIdentifier().getText(); + } + + @Override + public String getNamespace() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getDescription() { + // TODO Auto-generated method stub + return null; + } + + public void stream(AxiomIdentifierResolver resolver, AxiomStatementStreamListener listener) { + stream(resolver, listener, Optional.empty()); + } + + private void stream(AxiomIdentifierResolver resolver, AxiomStatementStreamListener listener, + Optional> emitOnly) { + AxiomAntlrVisitor visitor = new AxiomAntlrVisitor<>(sourceName, resolver, listener, emitOnly.orElse(null)); + visitor.visit(root); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java new file mode 100644 index 00000000000..835deb511c3 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomStatementStreamBuilder.java @@ -0,0 +1,68 @@ +/* + * 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.axiom.lang.impl; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Optional; +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.evolveum.axiom.lang.api.stmt.AxiomStatementStreamListener; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.google.common.collect.Iterables; + + +public class AxiomStatementStreamBuilder implements AxiomStatementStreamListener { + + + private final ModelReactorContext context; + private final Deque queue = new LinkedList<>(); + + public AxiomStatementStreamBuilder(ModelReactorContext context, StatementTreeBuilder root) { + this.context = context; + queue.add(root); + } + + protected StatementTreeBuilder current() { + return queue.peek(); + } + + @Override + public void argument(AxiomIdentifier identifier, SourceLocation loc) { + argument0(identifier, loc); + } + + @Override + public void argument(String identifier, SourceLocation loc) { + argument0(identifier, loc); + } + + private void argument0(Object value, SourceLocation loc) { + current().setValue(value, loc); + + } + + @Override + public void startStatement(AxiomIdentifier statement, SourceLocation loc) throws AxiomSyntaxException { + Optional childDef = current().childDef(statement); + AxiomSyntaxException.check(childDef.isPresent(), loc , "Statement %s not allowed in %s", statement, current().identifier()); + queue.offerFirst(createBuilder(childDef.get(), loc)); + } + + private StatementTreeBuilder createBuilder(AxiomItemDefinition item, SourceLocation loc) { + return current().createChildNode(item.name(), loc); + } + + @Override + public void endStatement(SourceLocation loc) { + StatementTreeBuilder current = queue.poll(); + context.endStatement(current, loc); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java new file mode 100644 index 00000000000..140873ad471 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomSyntaxException.java @@ -0,0 +1,82 @@ +/* + * 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.axiom.lang.impl; + +import java.util.Optional; + +import org.jetbrains.annotations.Nullable; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.google.common.base.Strings; + +public class AxiomSyntaxException extends RuntimeException { + + + private final String source; + private final int line; + private final int charPositionInLine; + + public AxiomSyntaxException(@Nullable final String source, final int line, + final int charPositionInLine, final String message) { + this(source, line, charPositionInLine, message, null); + } + + public AxiomSyntaxException(@Nullable final String source, final int line, + final int charPositionInLine, final String message, @Nullable final Throwable cause) { + super(message, cause); + this.source = source; + this.line = line; + this.charPositionInLine = charPositionInLine; + } + + public final Optional getSource() { + return Optional.ofNullable(source); + } + + public final int getLine() { + return line; + } + + public final int getCharPositionInLine() { + return charPositionInLine; + } + + public String getFormattedMessage() { + final StringBuilder sb = new StringBuilder(getMessage()); + if (source != null) { + sb.append(" in source "); + sb.append(source); + } + if (line != 0) { + sb.append(" on line "); + sb.append(line); + if (charPositionInLine != 0) { + sb.append(" character "); + sb.append(charPositionInLine); + } + } + return sb.toString(); + } + + @Override + public String toString() { + return this.getClass().getName() + ": " + getFormattedMessage(); + } + + public static void check(boolean test, String source, int line, int posInLine, String format, Object... args) { + if(!test) { + throw new AxiomSyntaxException(source, line, posInLine, Strings.lenientFormat(format, args)); + } + + } + + public static void check(boolean present, SourceLocation loc, String format, Object... args) { + check(present, loc.getSource(), loc.getLine(), loc.getChar(),format, args); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomTypeDefinitionImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomTypeDefinitionImpl.java new file mode 100644 index 00000000000..2ab7885138e --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/AxiomTypeDefinitionImpl.java @@ -0,0 +1,77 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.checkerframework.checker.units.qual.K; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomIdentifierDefinition; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.Identifiable; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Multimap; + +import static com.evolveum.axiom.lang.api.AxiomBuiltIn.Item.*; + + +public class AxiomTypeDefinitionImpl extends AbstractAxiomBaseDefinition implements AxiomTypeDefinition { + + public static final Factory FACTORY =AxiomTypeDefinitionImpl::new; + private final Map items; + private final Optional superType; + private Optional argument; + private Collection identifiers; + + public AxiomTypeDefinitionImpl(AxiomIdentifier keyword, AxiomIdentifier value, List> children, + Multimap> keywordMap) { + super(keyword, value, children, keywordMap); + ImmutableMap.Builder builder = ImmutableMap.builder(); + putAll(builder, children(ITEM_DEFINITION.name(), AxiomItemDefinition.class)); + items = builder.build(); + superType = first(SUPERTYPE_REFERENCE.name(), AxiomTypeDefinition.class); + argument = firstValue(ARGUMENT.name(), AxiomIdentifier.class) + .flatMap((AxiomIdentifier k) -> item(k)); + + identifiers = children(Item.IDENTIFIER_DEFINITION.name()).stream().map(idDef -> { + Set members = idDef.children(Item.ID_MEMBER.name()).stream() + .map(k -> item((AxiomIdentifier) k.value()).get()).collect(Collectors.toSet()); + AxiomIdentifier space = idDef.firstValue(ID_SPACE.name(), AxiomIdentifier.class).get(); + AxiomIdentifierDefinition.Scope scope = AxiomIdentifierDefinition.scope(idDef.firstValue(ID_SCOPE.name(), Object.class).get().toString()); + return AxiomIdentifierDefinition.from(space, scope, members); + }).collect(Collectors.toList()); + } + + @Override + public Optional argument() { + if (!argument.isPresent() && superType().isPresent()) { + return superType().get().argument(); + } + return argument; + } + + @Override + public Optional superType() { + return superType; + } + + @Override + public Map items() { + return items; + } + + @Override + public Collection identifiers() { + return identifiers; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/BasicStatementRule.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/BasicStatementRule.java new file mode 100644 index 00000000000..9e140c1e450 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/BasicStatementRule.java @@ -0,0 +1,145 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Type; +import com.evolveum.axiom.lang.api.AxiomIdentifierDefinition; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + + +public enum BasicStatementRule implements StatementRule { + + REQUIRE_REQUIRED_ITEMS(all(),all()) { + @Override + public void apply(StatementRuleContext rule) throws AxiomSemanticException { + AxiomTypeDefinition typeDef = rule.typeDefinition(); + for(AxiomItemDefinition required : typeDef.requiredItems()) { + rule.requireChild(required).unsatisfiedMessage(() -> rule.error("%s does not have required statement %s")); + rule.apply((ctx) -> {}); + } + } + }, + + COPY_ARGUMENT_VALUE(all(),all()) { + + @Override + public void apply(StatementRuleContext rule) throws AxiomSemanticException { + Optional argument = rule.typeDefinition().argument(); + if(argument.isPresent() && rule.optionalValue().isPresent()) { + rule.apply(ctx -> ctx.createEffectiveChild(argument.get().name(), ctx.requireValue())); + } + } + }, + REGISTER_TO_IDENTIFIER_SPACE(all(),all()) { + + @Override + public void apply(StatementRuleContext rule) throws AxiomSemanticException { + Collection idDefs = rule.typeDefinition().identifiers(); + if(!idDefs.isEmpty()) { + rule.apply(ctx -> { + for (AxiomIdentifierDefinition idDef : idDefs) { + IdentifierSpaceKey key = keyFrom(idDef, rule); + ctx.register(idDef.space(), idDef.scope(), key); + } + + }); + } + } + }, + /* + * Not needed - registration is handled by identifier statement + REGISTER_TYPE(items(Item.TYPE_DEFINITION), types(Type.TYPE_DEFINITION)) { + + @Override + public void apply(StatementRuleContext rule) throws AxiomSemanticException { + AxiomIdentifier typeName = rule.requireValue(); + rule.apply(ctx -> ctx.registerAsGlobalItem(typeName)); + } + }, + */ + EXPAND_TYPE_REFERENCE(all(), types(Type.TYPE_REFERENCE)) { + @Override + public void apply(StatementRuleContext rule) throws AxiomSemanticException { + AxiomIdentifier type = rule.requireValue(); + Requirement> typeDef = rule.requireGlobalItem(AxiomTypeDefinition.IDENTIFIER_SPACE, AxiomTypeDefinition.identifier(type)); + rule.apply(ctx -> { + ctx.replace(typeDef); + }); + rule.errorMessage(() -> rule.error("type '%s' was not found.", type)); + } + }; +/* + ADD_SUPERTYPE(items(), types(Type.TYPE_DEFINITION)) { + + @Override + public void apply(StatementRuleContext rule) throws AxiomSemanticException { + Optional superType = rule.optionalChildValue(Item.SUPERTYPE_REFERENCE, AxiomIdentifier.class); + if(superType.isPresent()) { + Requirement> req = rule.requireGlobalItem(Item.TYPE_DEFINITION, superType.get()); + rule.apply((ctx) -> { + //ctx.builder().add(Item.SUPERTYPE_REFERENCE, req.get()); + }); + rule.errorMessage(() -> { + if(!req.isSatisfied()) { + return rule.error("Supertype %s is not defined", superType.get()); + } + return null; + }); + } + } + };*/ + + private final Set items; + private final Set types; + + + private BasicStatementRule(Set items, Set types) { + this.items = ImmutableSet.copyOf(items); + this.types = ImmutableSet.copyOf(types); + } + + static IdentifierSpaceKey keyFrom(AxiomIdentifierDefinition idDef, StatementRuleContext ctx) { + ImmutableMap.Builder components = ImmutableMap.builder(); + for(AxiomItemDefinition cmp : idDef.components()) { + components.put(cmp.name(), ctx.requiredChildValue(cmp, Object.class)); + } + return IdentifierSpaceKey.from(components.build()); + } + + @Override + public boolean isApplicableTo(AxiomItemDefinition definition) { + return (items.isEmpty() || items.contains(definition.name())) + && (types.isEmpty() || types.contains(definition.type().name())); + } + + private static ImmutableSet types(AxiomTypeDefinition... types) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (AxiomTypeDefinition item : types) { + builder.add(item.name()); + } + return builder.build(); + + } + + private static ImmutableSet items(AxiomItemDefinition... items) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (AxiomItemDefinition item : items) { + builder.add(item.name()); + } + return builder.build(); + } + + private static ImmutableSet all() { + return ImmutableSet.of(); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DefaultItemDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DefaultItemDefinition.java new file mode 100644 index 00000000000..2df8066b9a7 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DefaultItemDefinition.java @@ -0,0 +1,46 @@ +/* + * 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.axiom.lang.impl; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; + +class DefaultItemDefinition implements AxiomItemDefinition { + + private AxiomIdentifier identifier; + private AxiomTypeDefinition type; + + public DefaultItemDefinition(AxiomIdentifier identifier, AxiomTypeDefinition type) { + super(); + this.identifier = identifier; + this.type = type; + } + + @Override + public AxiomIdentifier name() { + return identifier; + } + + @Override + public String documentation() { + return null; + } + + @Override + public AxiomTypeDefinition type() { + return type; + } + + @Override + public boolean required() { + // TODO Auto-generated method stub + return false; + } + + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DefaultTypeDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DefaultTypeDefinition.java new file mode 100644 index 00000000000..2db4cbf9f03 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DefaultTypeDefinition.java @@ -0,0 +1,62 @@ +/* + * 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.axiom.lang.impl; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomIdentifierDefinition; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; + +class DefaultTypeDefinition implements AxiomTypeDefinition { + + private AxiomIdentifier identifier; + private Map items; + + public DefaultTypeDefinition(AxiomIdentifier identifier, Map items) { + super(); + this.identifier = identifier; + this.items = items; + } + + @Override + public AxiomIdentifier name() { + return identifier; + } + + @Override + public String documentation() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Optional argument() { + return Optional.empty(); + } + + @Override + public Optional superType() { + return Optional.empty(); + } + + @Override + public Map items() { + return items; + } + + + @Override + public Collection identifiers() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/Deffered.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/Deffered.java new file mode 100644 index 00000000000..2e6bfdf74b6 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/Deffered.java @@ -0,0 +1,40 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; + +import com.google.common.base.Preconditions; + +class Deffered extends Requirement.Delegated { + + private Object ret; + + Deffered(Requirement delegate) { + ret = delegate; + } + + @Override + Requirement delegate() { + if(ret instanceof Requirement) { + return (Requirement) ret; + } + return null; + } + + @Override + public boolean isSatisfied() { + if(delegate() != null) { + if(delegate().isSatisfied()) { + ret = delegate().get(); + } else { + return false; + } + } + return true; + } + + @Override + public T get() { + Preconditions.checkState(isSatisfied(), "Requirement was not satisfied"); + return (T) ret; + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DelegatedRequirement.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DelegatedRequirement.java new file mode 100644 index 00000000000..f83c3ddd823 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/DelegatedRequirement.java @@ -0,0 +1,7 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; + +import com.google.common.base.Preconditions; + + diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/ModelReactorContext.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/ModelReactorContext.java new file mode 100644 index 00000000000..624177ca445 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/ModelReactorContext.java @@ -0,0 +1,253 @@ +package com.evolveum.axiom.lang.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.Supplier; + +import javax.management.RuntimeErrorException; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.concepts.Lazy; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Type; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomSchemaContext; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.evolveum.axiom.lang.impl.AxiomStatementImpl.Factory; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; + +import org.jetbrains.annotations.Nullable; + +public class ModelReactorContext implements AxiomIdentifierResolver { + + private static final AxiomIdentifier ROOT = AxiomIdentifier.from("root", "root"); + + private static final String AXIOM_LANG_RESOURCE = "/axiom-lang.axiom"; + + private static final Lazy BASE_LANGUAGE_SOURCE = Lazy.from(() -> { + InputStream stream = AxiomBuiltIn.class.getResourceAsStream(AXIOM_LANG_RESOURCE); + try { + return AxiomStatementSource.from(AXIOM_LANG_RESOURCE, stream); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + public static final Lazy BASE_LANGUAGE = Lazy.from(() -> { + ModelReactorContext reactor = boostrapReactor(); + return reactor.computeSchemaContext(); + }); + + public static final ModelReactorContext reactor(AxiomSchemaContext context) { + ModelReactorContext reactorContext = new ModelReactorContext(context); + defaults(reactorContext); + return reactorContext; + } + + public static final ModelReactorContext boostrapReactor() { + ModelReactorContext reactorContext = new ModelReactorContext(AxiomSchemaContextImpl.boostrapContext()); + defaults(reactorContext); + + return reactorContext; + } + + public static final ModelReactorContext defaultReactor() { + return reactor(BASE_LANGUAGE.get()); + } + + private static void defaults(ModelReactorContext reactorContext) { + reactorContext.addRules(BasicStatementRule.values()); + reactorContext.addStatementFactory(Type.TYPE_DEFINITION.name(), AxiomTypeDefinitionImpl.FACTORY); + reactorContext.addStatementFactory(Type.ITEM_DEFINITION.name(), AxiomItemDefinitionImpl.FACTORY); + reactorContext.loadModelFromSource(BASE_LANGUAGE_SOURCE.get()); + } + + List> rules = new ArrayList<>(); + + private final AxiomSchemaContext boostrapContext; + + public ModelReactorContext(AxiomSchemaContext boostrapContext) { + this.boostrapContext = boostrapContext; + } + + Map> globalItems = new HashMap<>(); + + Map>> globalSpace = new HashMap<>(); + + Map> typeFactories = new HashMap<>(); + List outstanding = new ArrayList<>(); + List> roots = new ArrayList<>(); + + public AxiomSchemaContext computeSchemaContext() throws AxiomSemanticException { + boolean anyCompleted = false; + do { + anyCompleted = false; + List toCheck = outstanding; + outstanding = new ArrayList<>(); + + Iterator iterator = toCheck.iterator(); + while (iterator.hasNext()) { + StatementRuleContextImpl ruleCtx = iterator.next(); + if (ruleCtx.canProcess()) { + ruleCtx.perform(); + iterator.remove(); + anyCompleted = true; + } + } + // We add not finished items back to outstanding + outstanding.addAll(toCheck); + } while (anyCompleted); + + if (!outstanding.isEmpty()) { + failOutstanding(outstanding); + } + + return createSchemaContext(); + } + + private void failOutstanding(List report) { + StringBuilder messages = new StringBuilder("Can not complete models, following errors occured:\n"); + for (StatementRuleContextImpl rule : report) { + RuleErrorMessage exception = rule.errorMessage(); + if (exception != null) { + messages.append(exception.toString()).append("\n"); + + } + } + throw new AxiomSemanticException(messages.toString()); + + } + + private AxiomSchemaContext createSchemaContext() { + ImmutableMap.Builder>> roots = ImmutableMap.builder(); + for (Entry>> entry: globalSpace.entrySet()) { + ImmutableMap.Builder> space = ImmutableMap.builder(); + for (Entry> item : entry.getValue().entrySet()) { + space.put(item.getKey(), item.getValue().asLazy().get()); + } + roots.put(entry.getKey(), space.build()); + + } + return new AxiomSchemaContextImpl(roots.build()); + + } + + public void registerGlobalItem(AxiomIdentifier typeName, StatementContextImpl context) { + globalItems.put(typeName, context); + } + + + public void registerGlobal(AxiomIdentifier space, IdentifierSpaceKey key, StatementContextImpl item) { + StatementContextImpl previous = globalSpace(space).putIfAbsent(key, item); + if(previous != null) { + throw new AxiomSemanticException(item.startLocation() + Strings.lenientFormat("Item %s in %s identifier space is already defined at %s", item.optionalValue(), space, previous.startLocation())); + } + } + + private Map> globalSpace(AxiomIdentifier spaceId) { + return globalSpace.computeIfAbsent(spaceId, k -> new HashMap<>()); + } + + public Requirement> requireGlobalItem(AxiomIdentifier space, IdentifierSpaceKey key) { + return (Requirement) Requirement.retriableDelegate(() -> { + StatementContextImpl maybeCtx = globalSpace(space).get(key); + if (maybeCtx != null) { + return maybeCtx.asRequirement(); + } + return null; + }); + } + + public void addOutstanding(StatementRuleContextImpl rule) { + outstanding.add(rule); + } + + void endStatement(StatementTreeBuilder cur, SourceLocation loc) throws AxiomSemanticException { + if (cur instanceof StatementContextImpl) { + StatementContextImpl current = (StatementContextImpl) cur; + for (StatementRule statementRule : rules) { + if (statementRule.isApplicableTo(current.definition())) { + current.addRule(statementRule); + } + } + } + } + + public void addRules(StatementRule... rules) { + for (StatementRule statementRule : rules) { + this.rules.add(statementRule); + } + } + + public void loadModelFromSource(AxiomStatementSource statementSource) { + statementSource.stream(this, new AxiomStatementStreamBuilder(this, new Root())); + } + + @Override + public AxiomIdentifier resolveStatementIdentifier(@Nullable String prefix, @NotNull String localName) { + return AxiomIdentifier.axiom(localName); + } + + private class Root implements StatementTreeBuilder { + + @Override + public void setValue(Object value) { + // NOOP + } + + @Override + public Optional childDef(AxiomIdentifier statement) { + return boostrapContext.getRoot(statement); + } + + @Override + public StatementTreeBuilder createChildNode(AxiomIdentifier identifier, SourceLocation loc) { + StatementContextImpl ret = new StatementContextImpl<>(ModelReactorContext.this, null, + childDef(identifier).get(), loc); + roots.add(ret); + return ret; + } + + @Override + public AxiomIdentifier identifier() { + return ROOT; + } + + @Override + public void setValue(Object value, SourceLocation loc) { + + } + } + + public void addStatementFactory(AxiomIdentifier statementType, Factory factory) { + typeFactories.put(statementType, factory); + } + + public Factory typeFactory(AxiomTypeDefinition statementType) { + Optional current = Optional.of(statementType); + do { + Factory maybe = typeFactories.get(current.get().name()); + if (maybe != null) { + return maybe; + } + current = current.get().superType(); + } while (current.isPresent()); + + return (Factory) AxiomStatementImpl.factory(); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/Requirement.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/Requirement.java new file mode 100644 index 00000000000..d0fedce26e7 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/Requirement.java @@ -0,0 +1,163 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.base.Preconditions; + + +public interface Requirement { + + boolean isSatisfied(); + public T get(); + + public RuleErrorMessage errorMessage(); + + public static Requirement unsatisfied() { + return new Unsatified<>(); + } + + public static Requirement immediate(T value) { + return new Immediate<>(value); + } + + public static Requirement from(Supplier supplier) { + return new Suppliable<>(supplier); + } + + public static abstract class Abstract implements Requirement { + + + private Supplier errorMessage; + + @Override + public Requirement unsatisfiedMessage(Supplier unsatisfiedMessage) { + errorMessage = unsatisfiedMessage; + return this; + } + + + @Override + public RuleErrorMessage errorMessage() { + if(errorMessage != null) { + return errorMessage.get(); + } + return null; + } + } + + + public static final class Immediate extends Abstract { + + private final V value; + + @Override + public boolean isSatisfied() { + return true; + } + + public Immediate(V value) { + super(); + this.value = value; + } + + @Override + public V get() { + return value; + } + + } + + public static final class Suppliable extends Abstract { + + private final Supplier value; + + @Override + public boolean isSatisfied() { + return value.get() != null; + } + + public Suppliable(Supplier value) { + super(); + this.value = value; + } + + @Override + public V get() { + return value.get(); + } + + } + + public static final class Unsatified extends Abstract { + + @Override + public boolean isSatisfied() { + return false; + } + + @Override + public V get() { + throw new IllegalStateException("Requirement not satisfied"); + } + } + + public abstract class Delegated extends Abstract { + + abstract Requirement delegate(); + + @Override + public boolean isSatisfied() { + return delegate().isSatisfied(); + } + + @Override + public T get() { + Preconditions.checkState(isSatisfied(), "Requirement was not satisfied"); + return delegate().get(); + } + } + + public final class RetriableDelegate extends Delegated { + + Object maybeDelegate; + + public RetriableDelegate(Supplier> lookup) { + maybeDelegate = lookup; + } + + @Override + Requirement delegate() { + if(maybeDelegate instanceof Requirement) { + return (Requirement) maybeDelegate; + } + if(maybeDelegate instanceof Supplier) { + Requirement result = ((Supplier>) maybeDelegate).get(); + if(result != null) { + maybeDelegate = result; + return (Requirement) result; + } + + } + return unsatisfied(); + } + + } + + static Requirement retriableDelegate(Supplier> lookup) { + return new RetriableDelegate(lookup); + } + + default Requirement unsatisfiedMessage(Supplier unsatisfiedMessage) { + return this; + } + static Requirement from(Optional maybe) { + if(maybe.isPresent()) { + return immediate(maybe.get()); + } + return unsatisfied(); + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/RuleErrorMessage.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/RuleErrorMessage.java new file mode 100644 index 00000000000..067a6e25cb8 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/RuleErrorMessage.java @@ -0,0 +1,32 @@ +package com.evolveum.axiom.lang.impl; + +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.google.common.base.Strings; + +public class RuleErrorMessage { + + private SourceLocation location; + private String message; + + public SourceLocation location() { + return location; + } + + public String message() { + return message; + } + + private RuleErrorMessage(SourceLocation location, String message) { + this.location = location; + this.message = message; + } + + public static RuleErrorMessage from(SourceLocation loc, String format, Object... args) { + return new RuleErrorMessage(loc, Strings.lenientFormat(format, args)); + } + + @Override + public String toString() { + return location.toString() + ": " + message; + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContext.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContext.java new file mode 100644 index 00000000000..bb3f92e0686 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContext.java @@ -0,0 +1,33 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; +import java.util.function.Supplier; + +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.AxiomIdentifierDefinition.Scope; + +public interface StatementContext { + + V requireValue() throws AxiomSemanticException; + + AxiomItemDefinition definition(); + + void registerAsGlobalItem(AxiomIdentifier typeName) throws AxiomSemanticException; + + StatementContext createEffectiveChild(AxiomIdentifier axiomIdentifier, V value); + + Optional optionalValue(); + + void replace(Requirement> statement); + + StatementContext parent(); + + void register(AxiomIdentifier space, Scope scope, IdentifierSpaceKey key); + + V requireValue(Class type); + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContextImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContextImpl.java new file mode 100644 index 00000000000..103d4075323 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContextImpl.java @@ -0,0 +1,218 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.concepts.Lazy; +import com.evolveum.axiom.concepts.Optionals; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.AxiomIdentifierDefinition.Scope; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; +import com.evolveum.axiom.lang.impl.AxiomStatementImpl.Factory; +import com.evolveum.axiom.lang.impl.StatementRuleContext.Action; +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Multimap; + +public class StatementContextImpl implements StatementContext, StatementTreeBuilder { + + + + private final ModelReactorContext reactor; + private final AxiomItemDefinition definition; + private final StatementContextImpl parent; + + private SourceLocation startLocation; + private SourceLocation endLocation; + private SourceLocation valueLocation; + private Requirement> result; + + + StatementContextImpl(ModelReactorContext reactor, StatementContextImpl parent, AxiomItemDefinition definition, SourceLocation loc) { + this.parent = parent; + this.reactor = reactor; + this.definition = definition; + this.startLocation = loc; + AxiomStatementBuilder builder = new AxiomStatementBuilder<>(definition.name(), reactor.typeFactory(definition.type())); + this.result = new StatementContextResult<>(definition, builder); + } + + @Override + public void setValue(Object value) { + mutableResult().setValue((V) value); + } + + private StatementContextResult mutableResult() { + if(result instanceof StatementContextResult) { + return (StatementContextResult) result; + } + return null; + } + + boolean isChildAllowed(AxiomIdentifier child) { + return definition.type().item(child).isPresent(); + } + + + @SuppressWarnings("rawtypes") + @Override + public StatementContextImpl createChildNode(AxiomIdentifier identifier, SourceLocation loc) { + StatementContextImpl childCtx = new StatementContextImpl(reactor, this, childDef(identifier).get(), loc); + mutableResult().add(childCtx); + return childCtx; + } + + @Override + public String toString() { + return "Context(" + definition.name() + ")"; + } + + @Override + public Optional childDef(AxiomIdentifier child) { + return definition.type().item(child); + } + + @Override + public AxiomIdentifier identifier() { + return definition.name(); + } + + @Override + public V requireValue() throws AxiomSemanticException { + return AxiomSemanticException.checkNotNull(mutableResult().value(), definition, "must have argument specified."); + } + + @Override + public AxiomItemDefinition definition() { + return definition; + } + + @Override + public void registerAsGlobalItem(AxiomIdentifier typeName) throws AxiomSemanticException { + reactor.registerGlobalItem(typeName, this); + } + + + @Override + public StatementContext createEffectiveChild(AxiomIdentifier axiomIdentifier, V value) { + StatementContextImpl child = createChildNode(axiomIdentifier, null); + child.setValue(value); + return child; + } + + + public void registerRule(StatementRuleContextImpl rule) { + mutableResult().addRule(rule); + this.reactor.addOutstanding(rule); + } + + public SourceLocation startLocation() { + return startLocation; + } + + ModelReactorContext reactor() { + return reactor; + } + + public Optional> firstChild(AxiomItemDefinition child) { + return Optionals.first(children(child.name())); + } + + private Collection> children(AxiomIdentifier identifier) { + return (Collection) mutableResult().get(identifier); + } + + public void addRule(StatementRule statementRule) throws AxiomSemanticException { + statementRule.apply(new StatementRuleContextImpl(this,statementRule)); + } + + @Override + public Optional optionalValue() { + return Optional.ofNullable(mutableResult().value()); + } + + @Override + public void replace(Requirement> supplier) { + this.result = (Requirement) supplier; + } + + @Override + public StatementContext parent() { + return parent; + } + + @Override + public void setValue(Object value, SourceLocation loc) { + setValue(value); + this.valueLocation = loc; + } + + public Requirement> asRequirement() { + if (result instanceof StatementContextResult) { + return new Deffered<>(result); + } + return result; + } + + public Supplier> asLazy() { + return Lazy.from(() -> result.get()); + } + + public boolean isSatisfied() { + return result.isSatisfied(); + } + + + @Override + public void register(AxiomIdentifier space, Scope scope, IdentifierSpaceKey key) { + switch (scope) { + case GLOBAL: + registerGlobal(space, key); + break; + case LOCAL: + registerLocalParent(space, key); + break; + default: + throw new IllegalStateException("Unsupporrted scope"); + } + } + + private void registerLocalParent(AxiomIdentifier space, IdentifierSpaceKey key) { + // TODO Auto-generated method stub + + } + + private void registerGlobal(AxiomIdentifier space, IdentifierSpaceKey key) { + reactor.registerGlobal(space, key, this); + } + + @Override + public V requireValue(Class type) { + if(result instanceof StatementContextResult) { + return type.cast(((StatementContextResult) result).value()); + } + return null; + } + + public Requirement> requireChild(AxiomItemDefinition required) { + return Requirement.retriableDelegate(() -> { + if(mutableResult() != null) { + return (Requirement) firstChild(required).map(StatementContextImpl::asRequirement).orElse(null); + } + Optional> maybe = result.get().first(required); + + return Requirement.from(maybe); + }); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContextResult.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContextResult.java new file mode 100644 index 00000000000..d973b27607a --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementContextResult.java @@ -0,0 +1,75 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +class StatementContextResult implements Requirement> { + + private V value; + private final List> childrenList = new ArrayList<>(); + private final Multimap> children = HashMultimap.create(); + private AxiomStatementBuilder builder; + + private final List> rules = new ArrayList<>(); + + public StatementContextResult(AxiomItemDefinition definition, AxiomStatementBuilder builder) { + super(); + this.builder = builder; + } + + public void setValue(V value) { + this.value = value; + this.builder.setValue(value); + } + + public void add(StatementContextImpl childCtx) { + childrenList.add(childCtx); + children.put(childCtx.identifier(), childCtx); + builder.add(childCtx.identifier(), childCtx.asLazy()); + } + public V value() { + return value; + } + + public Collection> get(AxiomIdentifier identifier) { + return children.get(identifier); + } + + @Override + public boolean isSatisfied() { + for (StatementRuleContextImpl rule : rules) { + if(!rule.isApplied()) { + return false; + } + } + for (StatementContextImpl rule : childrenList) { + if(!rule.isSatisfied()) { + return false; + } + } + return true; + } + + @Override + public AxiomStatement get() { + return builder.get(); + } + + public void addRule(StatementRuleContextImpl rule) { + this.rules.add(rule); + } + + @Override + public RuleErrorMessage errorMessage() { + // TODO Auto-generated method stub + return null; + } +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRule.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRule.java new file mode 100644 index 00000000000..61ecad414b0 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRule.java @@ -0,0 +1,13 @@ +package com.evolveum.axiom.lang.impl; + +import com.evolveum.axiom.lang.api.AxiomItemDefinition; + +public interface StatementRule { + + String name(); + + boolean isApplicableTo(AxiomItemDefinition definition); + + void apply(StatementRuleContext rule) throws AxiomSemanticException; + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRuleContext.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRuleContext.java new file mode 100644 index 00000000000..21d42969931 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRuleContext.java @@ -0,0 +1,40 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; +import java.util.function.Supplier; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; + +public interface StatementRuleContext { + + Optional optionalChildValue(AxiomItemDefinition supertypeReference, Class type); + + V requiredChildValue(AxiomItemDefinition supertypeReference, Class type) throws AxiomSemanticException; + + V requireValue() throws AxiomSemanticException; + + StatementRuleContext apply(StatementRuleContext.Action action); + + StatementRuleContext errorMessage(Supplier errorFactory); + + RuleErrorMessage error(String format, Object... arguments); + + public interface Action { + + void apply(StatementContext context) throws AxiomSemanticException; + } + + AxiomTypeDefinition typeDefinition(); + + Optional optionalValue(); + + Requirement> requireGlobalItem(AxiomIdentifier space, IdentifierSpaceKey key); + + Requirement> requireChild(AxiomItemDefinition required); + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRuleContextImpl.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRuleContextImpl.java new file mode 100644 index 00000000000..3f3a23f6093 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementRuleContextImpl.java @@ -0,0 +1,120 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.IdentifierSpaceKey; +import com.evolveum.axiom.lang.api.stmt.AxiomStatement; +import com.sun.net.httpserver.Authenticator.Result; +public class StatementRuleContextImpl implements StatementRuleContext { + + private final StatementContextImpl context; + private final StatementRule rule; + private final List> requirements = new ArrayList<>(); + private Action action; + private Supplier errorReport = () -> null; + private boolean applied = false; + + public StatementRuleContextImpl(StatementContextImpl context, StatementRule rule) { + this.context = context; + this.rule = rule; + } + + public StatementRule rule() { + return rule; + } + + @Override + public Optional optionalChildValue(AxiomItemDefinition child, Class type) { + return (Optional) context.firstChild(child).flatMap(v -> v.optionalValue()); + } + + @Override + public Requirement> requireGlobalItem(AxiomIdentifier space, + IdentifierSpaceKey key) { + return requirement(context.reactor().requireGlobalItem(space, key)); + } + + private Requirement requirement(Requirement req) { + this.requirements.add(req); + return req; + } + + @Override + public StatementRuleContextImpl apply(Action action) { + this.action = action; + context.registerRule(this); + return this; + } + + public boolean canProcess() { + for (Requirement requirement : requirements) { + if (!requirement.isSatisfied()) { + return false; + } + } + return true; + } + + public void perform() throws AxiomSemanticException { + this.action.apply(context); + this.applied = true; + } + + @Override + public V requiredChildValue(AxiomItemDefinition supertypeReference, Class type) + throws AxiomSemanticException { + StatementContext ctx = context.firstChild(supertypeReference).get(); + return (V) ctx.requireValue(type); + } + + @Override + public V requireValue() throws AxiomSemanticException { + return context.requireValue(); + } + + public boolean isApplied() { + return applied; + } + + @Override + public StatementRuleContext errorMessage(Supplier errorFactory) { + this.errorReport = errorFactory; + return this; + } + + RuleErrorMessage errorMessage() { + return errorReport.get(); + } + + @Override + public String toString() { + return context.toString() + ":" + rule; + } + + @Override + public AxiomTypeDefinition typeDefinition() { + return context.definition().type(); + } + + @Override + public Optional optionalValue() { + return context.optionalValue(); + } + + @Override + public RuleErrorMessage error(String format, Object... arguments) { + return RuleErrorMessage.from(context.startLocation(), format, arguments); + } + + @Override + public Requirement> requireChild(AxiomItemDefinition required) { + return context.requireChild(required); + } + +} diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementTreeBuilder.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementTreeBuilder.java new file mode 100644 index 00000000000..37154cda505 --- /dev/null +++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/impl/StatementTreeBuilder.java @@ -0,0 +1,22 @@ +package com.evolveum.axiom.lang.impl; + +import java.util.Optional; + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.stmt.SourceLocation; + +public interface StatementTreeBuilder { + + void setValue(Object value); + + Optional childDef(AxiomIdentifier statement); + + AxiomIdentifier identifier(); + + void setValue(Object value, SourceLocation loc); + + StatementTreeBuilder createChildNode(AxiomIdentifier identifier, SourceLocation loc); + + +} diff --git a/infra/axiom/src/main/resources/axiom-lang.axiom b/infra/axiom/src/main/resources/axiom-lang.axiom new file mode 100644 index 00000000000..a0f5d8f300b --- /dev/null +++ b/infra/axiom/src/main/resources/axiom-lang.axiom @@ -0,0 +1,212 @@ +model axiom-lang { + + root model { + documentation """ + Axiom Model + """; + type AxiomModel; + } + + type AxiomImportDeclaration { + argument prefix; + + item prefix { + type AxiomIdentifier; + } + + item uri { + type string; + } + } + + type AxiomModel { + extends AxiomBaseDefinition; + + item import { + type AxiomImportDeclaration; + } + + item root { + type AxiomRootDefinition; + } + + item type { + documentation """ + Type Declaration + """; + type AxiomTypeDefinition; + } + + item namespace { + type string; + } + + item version { + type string; + } + + // TODO move to prism schema; consider renaming to objectType? + item object { + type PrismObjectDefinition; + } + + // TODO move to prism schema; consider renaming to containerType? + item container { + type PrismContainerDefinition; + } + + // TODO move to prism schema; consider renaming to referenceType? + item reference { + type PrismReferenceDefinition; + } + + // TODO move to prism schema + item item { + type PrismItemDefinition; + } + } + + type AxiomRootDefinition { + extends AxiomItemDefinition; + identifier name { + scope global; + space AxiomRootDefinition; + } + } + + type AxiomBaseDefinition { + argument name; + + item name { + type AxiomIdentifier; + } + + item documentation { + type string; + } + + item since { + type SemanticVersion; + } + } + + type AxiomIdentifierDefinition { + argument key; + item key { + type AxiomIdentifier; + minOccurs "1"; + } + item scope { + type string; + minOccurs "1"; + } + item space { + type AxiomIdentifier; + minOccurs "1"; + } + } + + type AxiomTypeDefinition { + extends AxiomBaseDefinition; + + identifier name { + scope global; + space AxiomTypeDefinition; + } + + item argument { + type AxiomIdentifier; + } + + item extends { + type AxiomTypeReference; + } + + item identifier { + type AxiomIdentifierDefinition; + } + + // TODO move to prism schema + item object { + type boolean; + } + + // TODO move to prism schema + item container { + type boolean; + } + + // TODO move to prism schema + item objectReference { + type boolean; + } + + // TODO reconsider this - strictly speaking this is part of "global type+item definition combo" + item itemName { + type AxiomIdentifier; + } + + item item { + type AxiomItemDefinition; + } + } + + type AxiomIdentifier { + } + + type AxiomItemDefinition { + extends AxiomBaseDefinition; + + identifier name { + space AxiomItemDefinition; + scope local; + } + + item type { + type AxiomTypeReference; + } + item minOccurs { + type string; + } + item maxOccurs { + type string; + } + } + + type AxiomReference { + item targetType { + type AxiomTypeReference; + } + } + + type AxiomTypeReference; + + // "Type library" (temporary) + + type string; + type boolean; + + type SemanticVersion { + extends string; + } + + // TODO move to prism schema; probably should be prism:ObjectDefinition + type PrismObjectDefinition { + extends AxiomTypeDefinition; + } + + // TODO move to prism schema; probably should be prism:ContainerDefinition + type PrismContainerDefinition { + extends AxiomTypeDefinition; + } + + // TODO move to prism schema; probably should be prism:ReferenceDefinition + type PrismReferenceDefinition { + extends AxiomTypeDefinition; + } + + // TODO move to prism schema; probably should be prism:ItemDefinition + type PrismItemDefinition { + extends AxiomItemDefinition; + } +} diff --git a/infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java b/infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java new file mode 100644 index 00000000000..a3e28068d09 --- /dev/null +++ b/infra/axiom/src/test/java/com/evolveum/axiom/lang/test/TestAxiomParser.java @@ -0,0 +1,115 @@ +/* + * 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.axiom.lang.test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +import org.testng.annotations.Test; + + +import com.evolveum.axiom.api.AxiomIdentifier; +import com.evolveum.axiom.concepts.Lazy; +import com.evolveum.axiom.lang.api.AxiomBuiltIn; +import com.evolveum.axiom.lang.api.AxiomItemDefinition; +import com.evolveum.axiom.lang.api.AxiomSchemaContext; +import com.evolveum.axiom.lang.api.AxiomTypeDefinition; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Item; +import com.evolveum.axiom.lang.api.AxiomBuiltIn.Type; + +import com.evolveum.axiom.lang.impl.AxiomStatementSource; + +import com.evolveum.axiom.lang.impl.AxiomSyntaxException; +import com.evolveum.axiom.lang.impl.ModelReactorContext; +import com.evolveum.midpoint.tools.testng.AbstractUnitTest; + +public class TestAxiomParser extends AbstractUnitTest { + + private static final String COMMON_DIR_PATH = "src/test/resources/"; + private static final String BASE_EXAMPLE = "base-example.axiom"; + private static final String COMMON_CORE = "common-core.axiom"; + private static final String SCRIPTING = "scripting.axiom"; + private static final String AXIOM_BUILTIN_TYPES = "axiom-base-types.axiom"; + private static final Lazy AXIOM_TYPES_SCHEMA = Lazy.from(() -> { + try { + InputStream stream = new FileInputStream(COMMON_DIR_PATH + AXIOM_BUILTIN_TYPES); + return AxiomStatementSource.from(AXIOM_BUILTIN_TYPES, stream); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + + @Test + public void axiomSelfDescribingTest() throws IOException, AxiomSyntaxException { + ModelReactorContext bootstrapContext = ModelReactorContext.boostrapReactor(); + AxiomSchemaContext modelContext = bootstrapContext.computeSchemaContext(); + assertTypedefBasetype(modelContext.getType(Type.TYPE_DEFINITION.name())); + + AxiomItemDefinition modelDef = modelContext.getRoot(Item.MODEL_DEFINITION.name()).get(); + assertEquals(modelDef.name(), Item.MODEL_DEFINITION.name()); + + // Default reactor has Axiom model already loaded + ModelReactorContext folowupContext = ModelReactorContext.reactor(modelContext); + //folowupContext.loadModelFromSource(statementSource); + AxiomSchemaContext selfparsedContext = bootstrapContext.computeSchemaContext(); + assertNotNull(selfparsedContext.getRoot(Item.MODEL_DEFINITION.name())); + assertTrue(selfparsedContext.getType(Type.IDENTIFIER_DEFINITION.name()).get().item(Item.ID_MEMBER.name()).get().required()); + } + + + private void assertTypedefBasetype(Optional optional) { + AxiomTypeDefinition typeDef = optional.get(); + assertNotNull(typeDef); + assertEquals(typeDef.superType().get().name(), Type.BASE_DEFINITION.name()); + } + + + private void assertInstanceOf(Class clz, Object value) { + assertTrue(clz.isInstance(value)); + } + + @Test + public void moduleHeaderTest() throws IOException, AxiomSyntaxException { + AxiomSchemaContext context = parseFile(BASE_EXAMPLE); + assertNotNull(context.getType(AxiomIdentifier.axiom("Example")).get()); + } + + @Test + public void commonCoreTest() throws IOException, AxiomSyntaxException { + AxiomSchemaContext context = parseFile(COMMON_CORE); + } + + @Test + public void scriptingTest() throws IOException, AxiomSyntaxException { + AxiomSchemaContext context = parseFile(SCRIPTING); + } + + private AxiomSchemaContext parseFile(String name) throws AxiomSyntaxException, FileNotFoundException, IOException { + return parseInputStream(name, new FileInputStream(COMMON_DIR_PATH + name)); + } + + private AxiomSchemaContext parseInputStream(String name, InputStream stream) throws AxiomSyntaxException, FileNotFoundException, IOException { + return parseInputStream(name, stream, AxiomBuiltIn.Item.MODEL_DEFINITION); + } + + private AxiomSchemaContext parseInputStream(String name, InputStream stream, AxiomItemDefinition rootItemDefinition) throws AxiomSyntaxException, FileNotFoundException, IOException { + ModelReactorContext reactorContext =ModelReactorContext.defaultReactor(); + AxiomStatementSource statementSource = AxiomStatementSource.from(name, stream); + reactorContext.loadModelFromSource(statementSource); + reactorContext.loadModelFromSource(AXIOM_TYPES_SCHEMA.get()); + return reactorContext.computeSchemaContext(); + } + + +} diff --git a/infra/axiom/src/test/resources/axiom-base-types.axiom b/infra/axiom/src/test/resources/axiom-base-types.axiom new file mode 100644 index 00000000000..93f34d62719 --- /dev/null +++ b/infra/axiom/src/test/resources/axiom-base-types.axiom @@ -0,0 +1,19 @@ +// 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. + +// This is "common-core.axiom" file containing core definitions from the common-3 namespace. +// Due to its size the common-3 schema is distributed across multiple files. + +model axiom-base-types { + + namespace "http://midpoint.evolveum.com/xml/ns/public/common/common-3"; + version "0.1.0"; + + type uri; + type int; + type binary; + type dateTime; + +} diff --git a/infra/axiom/src/test/resources/axiom-ideas.adoc b/infra/axiom/src/test/resources/axiom-ideas.adoc new file mode 100644 index 00000000000..8a6cebd37db --- /dev/null +++ b/infra/axiom/src/test/resources/axiom-ideas.adoc @@ -0,0 +1,33 @@ + + type ObjectReference { + extends AxiomReference; + item targetType; + item filter + } + + type ObjectFoo { + item link { + type ObjectReference { + targetType ShadowType; + } + } + + item language { + type string { + midpoint:valueEnumeration "015-105-04560"; + } + } + } + + type LanguageCode { + extends string; + } + + extension midpoint:EnumerationValidation { + target string; + item valueEnumeration { + type ObjectReference { + targetType LookupTable; + } + } + } diff --git a/infra/axiom/src/test/resources/base-example.axiom b/infra/axiom/src/test/resources/base-example.axiom new file mode 100644 index 00000000000..3906c3f40d5 --- /dev/null +++ b/infra/axiom/src/test/resources/base-example.axiom @@ -0,0 +1,72 @@ +// 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. + +model model-header { + + documentation """ + This is showcase of baseline Axiom model header. + + Standard model header should contain: + - description (optional) + - namespace (required) + - version (required) + - prefix (optional) + """; + + namespace "https://ns.evolveum.com/example/axiom/model-header"; + version "0.0.1"; + + + type Address; + type Link; + type PolyString; + + type Example { + documentation """ + Example complex type. This type does not have supertype. + Type may contain items. + """; + + item name { + type string; + } + } + + object User { + itemName user; + item address { // shorthand syntax 'container address type Address'; + type Address; + //identifier type; // All values should have unique value. + maxOccurs unbounded; + } + item name { + type PolyString; + } + item link { + type Link; + maxOccurs unbounded; + } + } + + object { + name User2; + itemName user; + } + + // Alternative representation of a prism object definition + // (doesn't interfere with item { type ... } in structured types) + object { + name User2; + itemName user2; + // ... + } + + // Yet another alternative representation of a prism object definition. + // To be decided. + type User3 { + object; + // ... + } +} diff --git a/infra/axiom/src/test/resources/common-core.axiom b/infra/axiom/src/test/resources/common-core.axiom new file mode 100644 index 00000000000..a188f4aa56b --- /dev/null +++ b/infra/axiom/src/test/resources/common-core.axiom @@ -0,0 +1,1211 @@ +// 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. + +// This is "common-core.axiom" file containing core definitions from the common-3 namespace. +// Due to its size the common-3 schema is distributed across multiple files. + +model common-core { + + namespace "http://midpoint.evolveum.com/xml/ns/public/common/common-3"; + version "3.0.0"; + + type Object { + object; + itemName object; + documentation """ + Common supertype for all identity objects. Defines basic properties + that each object must have to live in our system (identifier, name). + + All objects are identified by OID. The OID is an immutable identifier + (usually UUID). Except the OID all the objects have human-readable name. + The name is usually unique for each object type, but this is not a + strict requirement. + + Note: object type is fixed, it cannot be changed. The object retains its + type from the time it was created to the end of its life. + """; + item name { + type PolyString; + documentation """ + Human-readable, mutable name of the object. It + may also be an identifier (login name, group name). + It is usually unique in the respective context of + interpretation. E.g. the name of the UserType subtype + is usually unique in the whole system. + The name of the ShadowType subtype is usually unique in the + scope of resource (target system) that it belongs to. + + The name may not be human-readable in a sense to display + to a common end-user. It is intended to be displayed to + IDM system administrator. Therefore it may contain quite + a "ugly" structures such as LDAP DN or URL. + + Name is mutable. It is considered to be ordinary property + of the object. Therefore it can be changed by invoking + usual modifyObject operations. However, change of the name + may have side effects (rename process). + + Although name is specified as optional by this schema, it + is in fact mandatory for most object types. The reason for + specifying the name as optional is that the name may be + generated by the system instead of supplied by the clients. + However, all objects stored in the repository must have a name. + """; + // ObjectType.name + // 0 + // true + } + + // Note: the description property is referenced from various contexts (e.g. Object and Assignment). + // To avoid duplication we use global property definition. + // item description; // TODO make this work + + // The same is true for documentation. + // item documentation; // TODO make this work + + item subtype { + type string; + maxOccurs unbounded; + documentation """ + Type of the object. It is used to distinguish what a specific object + represents. Whether it is a different kind of organizational unit, project, + team, or different kind of user, etc. + """; + // ObjectType.subtype + // 15 + } + + item fetchResult { + type OperationResult; + documentation """ + Result of the operation that fetched this instance of the object. + It is mostly used to indicate that the object is not complete or + there is some problem with the object. This is used instead of + exception if the object is part of larger structures (lists as in + list/search operations or composite objects). If not present then + the "SUCCESS" state is assumed. + + This field is TRANSIENT. It must only be used in runtime. It should + never be stored in the repository. + """; + // a:operational + } + + // item extension; // TODO make this work + + item trigger { + type Trigger; + maxOccurs unbounded; + documentation """ + Triggers for this object. They drive invocations of corresponding trigger handlers + at specified time. + """; + // a:operational + } + + item parentOrg { // TODO or should we use 'Ref' suffix i.e. 'parentOrgRef'? + type ObjectReference; + maxOccurs unbounded; + documentation """ + Set of the orgs (organizational units, projects, teams) that the object relates to. + This usually means that the object belongs to them but it may have other meanings as well + (e.g. user manages an organizational unit). + """; + // tns:OrgType + // OrgType.parentOrganization + // 240 + // + } + + item tenant { + type ObjectReference; + documentation """ + Reference to the tenant to which this object belongs. It is a computed value set automatically + by midPoint. It is determined from the organizational structure. Even though this value is + computed it is also stored in the repository due to performance reasons. + """; + // tns:OrgType + // OrgType.tenant + // 250 + // true + } + + item lifecycleState { + type string; + documentation """ + Lifecycle state of the object. This property defines whether the + object represents a draft, proposed definition, whether it is active, + deprecated, and so on. + + There are few pre-defined lifecycle states. But custom lifecycle states + may also be defined. Pre-defined lifecycle states are: + + - draft: Definition of the new object in progress. The object is + NOT active. The definition may change at any moment. It is + not ready yet. + - proposed: Definition of a new object is ready for use, but there + is still a review process to be applied (e.g. approval). + The object is NOT active. However the definition should + not change in this state. + - active: Active and working definition. Ready to be used without + any unusual limitations. + - deprecated: Active definition which is being phased out. The + definition is still fully operational. But it should not + be used for new assignments. E.g. it should not be requested, + it should not be approved, etc. + - archived: Inactive historical definition. It is no longer used. + It is maintained only for historical, auditing and + sentimental reasons. + - failed: Unexpected error has occurred during object lifecycle. Result + of that event is that the object is rendered inactive. + The situation cannot be automatically remedied. Manual action + is needed. + """; + // ObjectType.lifecycleState + // 20 + since "3.5"; + // + } + + item operationExecution { + type OperationExecution; + maxOccurs unbounded; + documentation """ + Description of recent operations executed on this object (or related objects, e.g. shadows + in case of a focal object). The number of operations to be kept here is configurable. + """; + since "3.6"; + // true + } + + item lensContext { + type LensContext; + documentation """ + Model context describing executed operation. + """; + since "4.0"; + // true + } + + item policySituation { + type uri; + maxOccurs unbounded; + documentation """ + The policy situation(s) of this object. The situations are result of + evaluation of the policy rules. This property is recorded for each object + and can be used for reporting, diagnostics, target selection in certification + campaigns, etc. + """; + since "3.5"; + // true + } + + item policyException { + type PolicyException; + maxOccurs unbounded; + documentation """ + Recorded exception from a policy rule. The exceptions that are approved are + recoded here to avoid re-evaluating and re-approving them all the time. + This is EXPERIMENTAL functionality. It is likely to change in the near future. + """; + since "3.5"; + // true + } + + item diagnosticInformation { + type DiagnosticInformation; + maxOccurs unbounded; + documentation """ + Diagnostic information attached to this object. + """; + since "4.0"; + // true + } + + // Note that oid and version are not defined here. These are intrinsic parts of prism objects + // so they do not have to be mentioned in the schema. + // TODO: is this OK? See also related questions in ObjectReference type. + } + + type AssignmentHolder { + extends Object; + itemName assignmentHolder; + documentation """ + Abstract supertype for all object types that can have assignments. + """; + + item assignment { + type Assignment; + maxOccurs unbounded; + documentation """ + Set of object's assignments. + Assignments define the privileges and "features" that this object should have, that + this object is entitled to. Typical assignment will point to a role or define + a construction of an account. + + Assignments represent what the object SHOULD HAVE. The assignments represent a policy, + a desired state of things (cf. linkRef). + """; + // FocusType.assignmentKey + } + + item iteration { + type int; // TODO + documentation """ + Iteration number. Starts with 0. It is used to iteratively find unique identifier + for the object. + """; + // true + } + + item iterationToken { + type string; + documentation """ + Iteration token. String value that is usually a suffix to the identifier based + on iteration number. E.g. ".007". It is used to iteratively find unique identifier + for the object. + """; + // true + } + + item archetype { + type ObjectReference; + maxOccurs unbounded; + documentation """ + References to all applicable archetypes, including "indirect" archetypes such as archetype supertypes. + Contains references to active archetypes only. + + Note: the value of this reference is only updated when object is recomputed. + Therefore if a role definition changes then all the affected objects must be recomputed + for this reference to be consistent. + + This is an operational property. It is set and managed by the system. It is used + for efficient use of archetypes. + """; + // tns:ArchetypeType + // true + // AssignmentHolderType.archetypeRef + since "4.0"; + } + + item roleMembership { + type ObjectReference; + maxOccurs unbounded; + documentation """ + References to abstract roles (roles, orgs, services) that this focus currently belongs to - directly + or indirectly. This reference points to all the roles in the role hierarchy. It only points to + the roles that were evaluated as active during last recompute (conditions were true, validity + constraints not violated). + + Note: the value of this reference is only updated when a focal object is recomputed. + Therefore if a role definition changes then all the affected focal objects must be recomputed + for this reference to be consistent. + + Roles mentioned here are those that are NOT obtained via delegation, i.e. "deputy" relations. + Relations acquired by delegation are listed in delegatedRef item. + + This is an operational property. It is set and managed by the system. It is used + for efficient search of all current role members, e.g. for the purpose of displaying this + information in the GUI. + """; + // tns:AbstractRoleType + // true + // FocusType.roleMembershipRef + } + + item delegated { + type ObjectReference; + maxOccurs unbounded; + documentation """ + References to objects (abstract roles as well as users) obtained via delegation. + If A1 is a deputy of A, its delegatedRef contains a union of A, A.roleMembershipRef and + A.delegatedRef. + + This is an operational property. It is set and managed by the system. It is used + for efficient search of all current role members, e.g. for the purpose of displaying this + information in the GUI. + """; + // tns:FocusType + // true + since "3.5"; + } + + item roleInfluence { + type ObjectReference; + maxOccurs unbounded; + documentation """ + References to abstract roles (roles and orgs) that this focus may directly belong to. + This reference only points to the next role in the hierarchy. However, it is backed by + a "closure" index in the repository subsystem. Therefore it can efficiently support tree-like + queries. This reference points to the roles for whose the condition is not true. + Therefore it does not reliably show + who actually has a role. It shows potential role members - all the object that are possibly + influenced when a role definition changes. + + This is an operational property. It is set and managed by the system. It is used + for efficient search of all possible role members, e.g. for the purpose of recomputing + all role members after the role definition is changed. + + TODO. NOT IMPLEMENTED YET. EXPERIMENTAL. UNSTABLE. + """; + // tns:AbstractRoleType + // true + } + } + + type Focus { + extends AssignmentHolder; + itemName focus; + documentation """ + Abstract supertype for all object types that can be focus of full midPoint computation. + This basically means objects that have projections. But focal objects also have + activation, they may have personas, etc. + """; + item link { + type ObjectReference; + maxOccurs unbounded; + documentation """ + Set of shadows (projections) linked to this focal object. + E.g. a set of accounts linked to a user. This is the set of + shadows that belongs to the focal object in a sense + that these shadows represents the focal object on the resource. + E.g. The set of accounts that represent the same midPoint user (the + same physical person, they are "analogous"). + + Links define what the object HAS. The links reflect real state of things + (cf. assignment). + """; + // tns:ShadowType + } + + item persona { + type ObjectReference; + maxOccurs unbounded; + documentation """ + Set of personas linked to this focal object. + E.g. a set of virtual identities linked to a user. This is the set of + "secondary" focal objects that belongs to this focal object in a sense + that the current focal object is in control over the linked focal objects. + E.g. this reference can be used to link user object which specified a physical + person with his virtual identities (personas) that specify his identity as an + employee, system administrator, customer, etc. + The default meaning is that the personas are "analogous", i.e. the represent + different facets of the same physical person. However, this meaning may be + theoretically overridden by using various relation parameters in this reference. + + This reference define what the object HAS. The links reflect real state of + things (cf. assignment). + """; + // tns:FocusType + since "3.6"; + } + + item activation { + type Activation; + } + + item jpegPhoto { + type binary; + documentation """ + Photo corresponding to the user / org / role / service. + """; + // FocusType.jpegPhoto + } + + item costCenter { + type string; + documentation """ + The name, identifier or code of the cost center to which the object belongs. + + Please note that organization objects (OrgType) also have a costCenter property. + Therefore it is usual that if a user belongs to an organization the costCenter from + the organization is used. Therefore this property is usually used only for users that + do not belong to any organization or for users that have different cost center than + the one defined by the organization. + """; + // FocusType.costCenter + // 420 + } + + item locality { + type PolyString; + documentation """ + Primary locality of the object, the place where + the (e.g.) user usually works, the country, city or + building that he belongs to. The specific meaning + and form of this property is deployment-specific. + """; + // FocusType.locality + // 450 + } + + item preferredLanguage { + type string; + documentation """ + Indicates user's preferred language, usually for the purpose of localizing + user interfaces. The format is IETF language tag defined in BCP 47, where + underscore is used as a subtag separator. This is usually a ISO 639-1 two-letter + language code optionally followed by ISO 3166-1 two letter country code + separated by underscore. The languages that do not have country-specific + variants are usually specified by using a two-letter country code ("sk", + "cs", "tr"). Languages with country-specific variants have country-specific + subtags ("pt_BR", "zn_CN"). + If no value is specified in this property then system default locale is assumed. + + Examples: + - en_US + - sk + - cs + - pt_BR + """; + // FocusType.preferredLanguage + // 200 + // + } + + item locale { + type string; + documentation """ + Defines user's preference in displaying currency, dates and other items + related to location and culture. The format is IETF language tag defined in BCP 47, where + underscore is used as a subtag separator. This is usually a ISO 639-1 two-letter + language code optionally followed by ISO 3166-1 two letter country code + separated by underscore. The languages that do not have country-specific + variants are usually specified by using a two-letter country code ("sk", + "cs", "tr"). Languages with country-specific variants have country-specific + subtags ("pt_BR", "zn_CN"). + If not specified then system default locale is assumed. + + Examples: + - en_US + - sk + - cs + - pt_BR + """; + // FocusType.locale + // 210 + // + } + + item timezone { + type string; + documentation """ + User's preferred timezone. It is specified in the "tz database" (a.k.a "Olson") + format. If not specified then system default timezone is assumed. + + Examples: + - Europe/Bratislava + """; + // FocusType.timezone + // 220 + // + } + + item emailAddress { + type string; + documentation """ + E-Mail address of the user, org. unit, etc. This is the address + supposed to be used for communication with the + user, org. unit, etc. E.g. IDM system may send notifications + to the e-mail address. It is NOT supposed to be + full-featured e-mail address data structure + e.g. for the purpose of complex address-book application. + """; + // FocusType.emailAddress + // 300 + } + + item telephoneNumber { + type string; + documentation """ + Primary telephone number of the user, org. unit, etc. + """; + // FocusType.telephoneNumber + // 310 + } + + item credentials { + type Credentials; + documentation """ + The set of focus's credentials (such as passwords). + """; + // FocusType.credentials + } + } + + type User { + extends Focus; + documentation """ + User object represents a physical user of the system. + It differs from the account, as "account" represents a data structure in a target system while + "user" represents data structure in midPoint. One user typically has many accounts. + Properties of User object typically describe the user as a physical person. + Therefore the user object defines handful of properties that are commonly used to describe users + in the IDM solutions (employees, customers, partners, etc.) Custom extensions are possible by utilizing + the "extension" container. + """; + + item fullName { + type PolyString; + documentation """ + Full name of the user with all the decorations, + middle name initials, honorific title and any + other structure that is usual in the cultural + environment that the system operates in. This + element is intended to be displayed to + a common user of the system. + + Examples: + - cpt. Jack Sparrow + - William "Bootstrap" Turner + - James W. Random, PhD. + - Vladimir Iljic Lenin + - Josip Broz Tito + - Chuck Norris + """; + // UserType.fullName + // 100 + // true + } + + item givenName { + type PolyString; + documentation """ + Given name of the user. It is usually the first + name of the user, but the order of names may + differ in various cultural environments. This + element will always contain the name that was + given to the user at birth or was chosen + by the user. + + Examples: + - Jack + - Chuck + """; + // UserType.givenName + // 110 + // true + } + + item familyName { + type PolyString; + documentation """ + Family name of the user. It is usually the last + name of the user, but the order of names may + differ in various cultural environments. This + element will always contain the name that was + inherited from the family or was assigned + to a user by some other means. + + Examples: + - Sparrow + - Norris + """; + // UserType.familyName + // 120 + // true + } + + item additionalName { + type PolyString; + documentation """ + Middle name, patronymic, matronymic or any other name of a person. It is usually the + middle component of the name, however that may be culture-dependent. + + Examples: + - Walker + - John + - Iljic + """; + // UserType.additionalName + // 130 + } + + item nickName { + type PolyString; + documentation """ + Familiar or otherwise informal way to address a person. + + Examples: + - Bootstrap + - Bobby< + + The meaning of this property is to take part in the formatted full + name of the person, e.g. William "Bootstrap" Turner. It is not intended + to be used as a username or login name. This value is usually changeable + by the user itself and it defines how the user wants other to address him. + Therefore it is not ideal for use as an identifier. + """; + // UserType.nickname + // 140 + } + + item honorificPrefix { + type PolyString; + documentation """ + Honorific titles that go before the name. + Examples: + - cpt. + - Ing. + - Sir + + This property is single-valued. If more + than one title is applicable, they have to be represented in + a single string (concatenated) form in the correct order. + """; + // UserType.honorificPrefix + // 150 + } + + item honorificSuffix { + type PolyString; + documentation """ + Honorific titles that go after the name. + + Examples: + - PhD. + - KBE + + This property is single-valued. If more + than one title is applicable, they have to be represented in + a single string (concatenated) form in the correct order. + """; + // UserType.honorificSuffix + // 160 + } + + item title { + type PolyString; + documentation """ + User's title defining a work position or a primary role in the + organization. + Examples: + - CEO + - Security Officer + - Assistant + """; + // UserType.title + // 170 + } + + item employeeNumber { + type string; + documentation """ + Unique, business-oriented identifier of the employee. + Typically used as correlation identifier and for + auditing purposes. Should be immutable, but the + specific properties and usage are deployment-specific. + """; + // UserType.employeeNumber + // 400 + } + + item employeeType { + type string; + maxOccurs unbounded; + documentation """ + Employee type specification such as internal employee, + external or partner. The specific values are + deployment-specific. However it is generally assumed that this + will be enumeration of several type names or codes that define + "classes" of users. + + Even though this property is named "employeeType" due to the historical + reasons it is used in a more generic way to mean general type of user. + Therefore it can be used to distinguish employees from customers, etc. + + DEPRECATED: Use ObjectType.subtype + """; + // UserType.employeeType + // 410 + // true + // 3.8 + } + item organization { + type PolyString; + maxOccurs unbounded; + documentation """ + Name or (preferably) immutable identifier of organization that the user belongs to. + The format is deployment-specific. This property together with organizationalUnit + may be used to provide easy-to-use data about organizational membership of the user. + + This is multi-valued property to allow membership of a user to several + organizations. Please note that midPoint does not maintain ordering in + multi-value properties therefore this is not usable to model a complex + organization hierarchies. Use OrgType instead. + """; + // UserType.organization + // 430 + } + + item organizationalUnit { + type PolyString; + maxOccurs unbounded; + documentation """ + Name or (preferably) immutable identifier of organizational unit that the user belongs to. + The format is deployment-specific. This property together with organization + may be used to provide easy-to-use data about organizational membership of the user. + + This is multi-valued property to allow membership of a user to several + organizational units. Please note that midPoint does not maintain ordering in + multi-value properties therefore this is not usable to model a complex + organization hierarchies. Use OrgType instead. + """; + // UserType.organizationalUnit + // 440 + } + item adminGuiConfiguration { + type AdminGuiConfiguration; + documentation """ + Specifies the admin GUI configuration that should be used + by this user. + """; + since "3.5"; + // AdminGuiConfigurationType.adminGuiConfiguration + } + } + + type ObjectReference { + objectReference; + documentation """ + Reference to an object. It contains OID of the object that it + refers to. + """; + + // item description; // TODO make this work + + // item documentation; // TODO make this work + + item filter { + type SearchFilter; // TODO namespace of "query-3" + documentation """ + Filter that can be used to dynamically lookup the reference OID e.g. during imports. + It must not be used for normal operations. The filter may be stored in the repository + to avoid data loss. But even if it is stored it will not be used beyond initial + import or unless explicitly requested (e.g. by setting resolutionTime). + + Note: The filter will NOT be used if the OID in the reference is set. The OID always takes + precedence. + """; + } + + item resolutionTime { + type EvaluationTime; + // TODO defaultValue "import"; + documentation """ + Definition of the "time" when the reference will be resolved. Resolving the reference means using + the filter to get object(s) or OID(s). + + Import-time resolution means that the reference will be resolved once when the file is imported. + OID will be recorded in the reference and then only the OID will be used to follow the reference. + This is a very efficient method and it is the default. + + Run-time resolution means that the reference will be resolved every time that the reference is + evaluated. This is less efficient but it provides great flexibility as the filter may contain + expressions and therefore the reference target may dynamically change. + """; + } + + item referentialIntegrity { + type ReferentialIntegrity; + // TODO defaultValue "default"; + documentation """ + Definition of the behavior related to non-existence of object with specified target OID. + (Currently supported only at selected places in midPoint.) + """; + since "4.1"; + } + + item targetName { + type PolyString; + documentation """ + Cached name of the target object. + This is a ephemeral value. It is not stored in the repository. + It may be computed at object retrieval time or it may not be present at all. + This is NOT an authoritative information. Setting it or changing it will + not influence the reference meaning. OID is the only authoritative linking + mechanism. + """; + } + + // TODO what about (attributes) oid, type, and relation? + // Should they be listed here even if they are defined in PrismReferenceValue? + // But if not, why should we list filter, resolution time, referential integrity here, + // as they are also defined in PrismReferenceValue. + } + + item description { + type string; + documentation """ + Free-form textual description of the object. It is supposed to describe + the object or a construct that it is attached to. + + This information may be presented to midPoint users, even to ordinary end users. + For example role description may be presented to users when they are selecting + roles to request. Therefore the description should be written in a language that + the users can understand. + + Description is assumed to be a plan, non-formatted text. + Amount of white space is considered insignificant. E.g. leading and trailing + white space may be skipped, multiple spaces can be collapsed to one and so on. + """; + // ObjectType.description + // 10 + } + + item documentation { + type string; + documentation """ + Technical documentation for a particular object or construct. + + The purpose of this element is to document system configuration and behavior. + The documentation will not be presented to end users. In fact, it will probably + not be presented at all in midPoint user interface. This documentation element + is supposed to be a part of the technical documentation of midPoint deployment. + The tools than generate deployment configuration will look for these elements + and combine them to compiled documentation document. + + AsciiDoc formatting is assumed for this element. Any leading or trailing + whitespace is skipped. Indentation equivalent to he indentation of the first + non-blank line of text is also skipped. + """; + // ObjectType.documentation + // 11 + } + + // Example of short version of container definition. + container { + name Assignment; + itemName assignment; + + // item description; // TODO make this work + // item documentation; // TODO make this work + // item extension; // TODO make this work + + // ... + } + + container { + name Extension; + itemName extension; + documentation """ + Extension container that provides generic extensibility mechanism. + Almost any extension property can be placed in this container. + This mechanism is used to extend objects with new properties. + The extension is treated exactly the same as other object + properties by the code (storage, modifications, etc), except + that the system may not be able to understand their meaning. + """; + // ObjectType.extension + // 1000 + } + + object GenericObject { + extends Focus; + itemName genericObject; + documentation """ + Generic object for storing unknown (unexpected) object types. + + The generic object should be used if there is a need to + store a custom object (e.g KangarooType) at deployment-time. + The properties of such custom objects are to be placed in the + extension part of this object. The schema is not checked or + enforced for this type of objects if technically possible. + """; + item objectType { + type uri; // TODO + // deprecated + } + } + + container Trigger { + itemName trigger; + documentation """ + Defines triggers for an object. Trigger is an action that should take place + at specified time or under some other condition. + """; + item timestamp { + type dateTime; // TODO + documentation """ + The time when a trigger needs to be activated. + """; + } + item handlerUri { + type uri; // TODO + documentation """ + Handler URI indirectly specifies which class is responsible to handle the task. The handler will + to be used to handle trigger activation. + """; + } + item originDescription { + type string; + documentation """ + Short description of trigger origin, e.g. name of the mapping. + Used for diagnostic purposes. + """; + // true + since "4.0"; + } + item extension { // TODO + type Extension; + documentation """ + Extension container used to provide additional situation-specific information to the trigger. + """; + } + } + + container Metadata { + itemName metadata; + documentation """ + Meta-data about data creation, modification, etc. + It may apply to objects but also parts of the object (e.g. assignments). + + Meta-data only apply to successful operations. That is obvious for create, but it also applies + to modify. For obvious reasons there are no metadata about delete. + We keep no metadata about reading. That would be a huge performance hit. + + Meta-data only describe the last operation of its kind. E.g. there is a record of last + modification, last approval, etc. There is no history. The last operation overwrites data + about the previous operation. + + These data are informational only. They should not be used for security purposes (use auditing + subsystem for that). But presence of metadata simplifies system administration and may provide + some basic information "at the glance" which may be later confirmed by the audit logs. + + Meta-data are also supposed to be searchable. Therefore they may be used to quickly find + "candidate" objects for a closer examination. + """; + // true + // + // Metadata + + item requestTimestamp { + type dateTime; + documentation """ + The timestamp of "create" operation request. It is set once and should never be changed. + + In case of "background" processes to create object (e.g. create with approval) + this should be the timestamp when the process started. I.e. the timestamp when + the operation was requested. + """; + // MetadataType.requestTimestamp + // true + since "3.5"; + // + } + + item requestor { + type ObjectReference; + documentation """ + Reference to the user that requested the "create" operation for this object or assignment. + """; + // MetadataType.requestorRef + // true + // tns:UserType + // + } + + item requestorComment { + type string; + documentation """ + Comment of the user that requested the "create" operation for this object or assignment. + """; + // MetadataType.requestorComment + // true + since "3.7"; + } + + item createTimestamp { + type dateTime; + documentation """ + The timestamp of data creation. It is set once and should never be changed. + + In case of "background" processes to create object (e.g. create with approval) + this should be the timestamp when the process ended. I.e. the timestamp when + the operation was executed. + """; + // MetadataType.createTimestamp + // true + // true + since "3.5"; + } + + item creator { + type ObjectReference; + maxOccurs unbounded; + documentation """ + Reference to the user that approved the creation of the data (if there was such a user). + This is multi-value reference therefore multiple approvers may be recorded. However the order and + hierarchy of the approvers is lost. + """; + // MetadataType.createApproverRef + // true + // true + // tns:UserType + } + + item createApproverComment { + type string; + maxOccurs unbounded; + documentation """ + Comments of the approvers during the creation of the data. Note that these comments are in no + particular order, so basically it is not known who entered which comment. + """; + // MetadataType.createApprovalComment + // true + since "3.7"; + } + + item createApprovalTimestamp { + type dateTime; + documentation """ + The timestamp of creation approval. + """; + // MetadataType.createApprovalTimestamp + // true + since "3.5"; + } + + item createChannel { + type uri; + documentation """ + Channel in which the object was created. + """; + // MetadataType.createChannel + // true + // true + } + + item createTask { + type ObjectReference; + documentation """ + Reference to the task that created the object (if it was a persistent one). + """; + // MetadataType.createTaskRef + // true + // tns:TaskType + since "3.7"; + } + + item modifyTimestamp { + type dateTime; + // MetadataType.modifyTimestamp + // true + // true + } + + item modifier { + type ObjectReference; + // MetadataType.modifierRef + // true + // true + // tns:UserType + } + + item modifyApprover { + type ObjectReference; + maxOccurs unbounded; + // MetadataType.modifyApproverRef + // true + // true + // tns:UserType + } + + item modifyApprovalComment { + type string; + maxOccurs unbounded; + // MetadataType.modifyApprovalComment + // true + since "3.7"; + } + + item modifyChannel { + type uri; + // MetadataType.modifyChannel + // true + // true + } + + item modifyTask { + type ObjectReference; + // MetadataType.modifyTaskRef + // true + // tns:TaskType + since "3.7"; + } + + item lastProvisioningTimestamp { + type dateTime; + // MetadataType.lastProvisioningTimestamp + // true + since "3.6.1"; + } + + item certificationFinishedTimestamp { + type dateTime; + // MetadataType.certificationFinishedTimestamp + // true + since "3.7"; + } + + item certificationOutcome { + type string; + // MetadataType.certificationOutcome + // true + since "3.7"; + } + + item certifier { + type ObjectReference; + // MetadataType.certifierRef + // true + // tns:UserType + since "3.7"; + } + + item certifierComment { + type string; + maxOccurs unbounded; + // MetadataType.certifierComment + // true + since "3.7"; + } + + item originMappingName { + type string; + // MetadataType.originMappingName + // true + since "3.7"; + } + } + + // axiom + + // types-3 + type PolyString; + + // query-3 + type SearchFilter; + type EvaluationTime; + type ReferentialIntegrity; + + // ??? + type Extension; + + // common-3 + type OperationResult; + type OperationExecution; + type LensContext; + type PolicyException; + type DiagnosticInformation; + type Credentials; + type AdminGuiConfiguration; + type Activation; + + // should exist because of "container XXX" + type Trigger; + type Assignment; + +} diff --git a/infra/axiom/src/test/resources/scripting.axiom b/infra/axiom/src/test/resources/scripting.axiom new file mode 100644 index 00000000000..c484847ddc1 --- /dev/null +++ b/infra/axiom/src/test/resources/scripting.axiom @@ -0,0 +1,182 @@ +// 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. + +model scripting { + + namespace "http://midpoint.evolveum.com/xml/ns/public/model/scripting-3"; + version "3.0.0"; + + type ScriptingExpressionEvaluationOptions { + documentation """ + Options related to evaluation of scripting expression. + EXPERIMENTAL + In the future, these may become part of any scripting expression, allowing parts of a complex expression + to be evaluated differently from its other parts. + """; + item continueOnAnyError { + type boolean; + } + item hideOperationResults { + type boolean; + } + } + + type ScriptingVariablesDefinition { + documentation """ + Definition of scripting variables. + """; + item variable { + type ScriptingVariableDefinition; + maxOccurs unbounded; + } + } + + type ScriptingVariableDefinition { + documentation """ + Definition of a scripting variable. + Expression types other than path-based ones are to be considered EXPERIMENTAL. + """; + item name { + type string; + minOccurs 1; + } + item description { + type string; + } + item type { + type QName; + } + item maxOccurs { + type string; + } + item expression { + type Expression; + minOccurs 1; + } + } + + type ScriptingExpression { + itemName scriptingExpression; + documentation """ + Root of the expression type inheritance hierarchy. + """; + } + + type ExpressionPipeline { + extends ScriptingExpression; + documentation """ + Pipeline of expressions - they are executed one after another, + input sent to the pipeline as a whole is sent to the first expression. + Output from the N-th expression is sent as an input to the N+1-th expression. + Output of the last expression is considered to be the output of the whole + pipeline. + """; + item scriptingExpression { + type ScriptingExpression; // TODO make it works without this (i.e. as item ref) + maxOccurs unbounded; + } + } + + item pipeline { + type ExpressionPipeline; + // substitution of scriptingExpression, heterogeneous list item + } + + type ExpressionSequence { + extends ScriptingExpression; + documentation """ + Sequence of expressions - they are executed one after another, + input sent to the sequence as a whole is then sent individually + to each expression. Output of the last expression is considered + to be the output of the whole sequence. + """; + item scriptingExpression { + type ScriptingExpression; // TODO make it works without this (i.e. as item ref) + maxOccurs unbounded; + } + } + + item sequence { + type ExpressionSequence; + // substitution of scriptingExpression, heterogeneous list item + } + + type SearchExpression { + extends ScriptingExpression; + documentation """ + Queries the model for objects of a given type, optionally fulfilling given condition. + """; + + item type { + type QName; + documentation """ + Type whose instances are searched for. + """; + } + + item query { + type Query; + documentation """ + Query to apply when searching for instances. (Alternative to searchFilter. This is tried as the first one.) + """; + } + + item searchFilter { + type SearchFilter; + documentation """ + Filter to apply when searching for instances. (Alternative to query. This is tried as second one.) + """; + } + + item options { + type SelectorQualifiedGetOptions; + } + + item parameter { + type ActionParameterValue; + maxOccurs unbounded; + } + + item scriptingExpression { + type ScriptingExpression; // TODO make it works without this (i.e. as item ref) + documentation """ + Expression to evaluate for each object found. + """; + } + + item aggregateOutput { + type boolean; + documentation """ + Whether to aggregate and pass forward the output of expression evaluations that are done + for each object found. (Meaningful only if scriptingExpression is specified.) + Default is true for compatibility reasons. Set to false to optimize memory consumption. + """; + // since 3.7.1 + } + } + + item search { + type SearchExpression; + // substitution of scriptingExpression, heterogeneous list item + } + + // and so on ... + + // Types (temporary) + + type ActionParameterValue; + + // query-3 + type Query; + type SearchFilter; + + // common-3 + type Expression; + type SelectorQualifiedGetOptions; + + // basic + type QName; +} + diff --git a/infra/pom.xml b/infra/pom.xml index f4ae2546837..367caf9f695 100644 --- a/infra/pom.xml +++ b/infra/pom.xml @@ -21,6 +21,7 @@ midPoint Infrastructure + axiom util schema schema-pure-jaxb diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismContext.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismContext.java index 1a445ca5925..1f5f0c370f0 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismContext.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismContext.java @@ -14,6 +14,7 @@ import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; import com.evolveum.midpoint.prism.marshaller.JaxbDomHack; import com.evolveum.midpoint.prism.marshaller.ParsingMigrator; +import com.evolveum.midpoint.prism.metadata.ValueMetadataMockUpFactory; import com.evolveum.midpoint.prism.path.CanonicalItemPath; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.UniformItemPath; @@ -367,4 +368,16 @@ default ItemPath toPath(ItemPathType path) { @NotNull SchemaFactory schemaFactory(); + + /** + * TEMPORARY. WILL BE REMOVED AFTER PROTOTYPING IS OVER. + */ + @Experimental + void setValueMetadataMockUpFactory(ValueMetadataMockUpFactory factory); + + /** + * TEMPORARY. WILL BE REMOVED AFTER PROTOTYPING IS OVER. + */ + @Experimental + ValueMetadataMockUpFactory getValueMetadataMockUpFactory(); } diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismValue.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismValue.java index 816d5d64b45..ee826aff4ee 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismValue.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismValue.java @@ -25,6 +25,7 @@ import java.io.Serializable; import java.util.Collection; import java.util.Map; +import java.util.Optional; /** * @author semancik @@ -45,6 +46,9 @@ public interface PrismValue extends Visitable, PathVisitable, Serializable, Debu void setParent(Itemable parent); + @Experimental + Optional valueMetadata() throws SchemaException; + @NotNull ItemPath getPath(); diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ValueMetadata.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ValueMetadata.java new file mode 100644 index 00000000000..fb93b9f84f3 --- /dev/null +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/ValueMetadata.java @@ -0,0 +1,8 @@ +package com.evolveum.midpoint.prism; + +import com.evolveum.midpoint.util.annotation.Experimental; + +@Experimental +public interface ValueMetadata extends PrismContainerValue { + +} diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/metadata/ValueMetadataMockUpFactory.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/metadata/ValueMetadataMockUpFactory.java new file mode 100644 index 00000000000..d981d42ed64 --- /dev/null +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/metadata/ValueMetadataMockUpFactory.java @@ -0,0 +1,23 @@ +/* + * 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.prism.metadata; + +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.ValueMetadata; +import com.evolveum.midpoint.util.exception.SchemaException; + +import java.util.Optional; + +/** + * Provides mock up value metadata for given prism value. + */ +public interface ValueMetadataMockUpFactory { + + Optional createValueMetadata(PrismValue value) throws SchemaException; + +} diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContextImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContextImpl.java index 59337cd5e98..eda2ae4492c 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContextImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContextImpl.java @@ -23,6 +23,7 @@ import com.evolveum.midpoint.prism.impl.lex.LexicalProcessor; import com.evolveum.midpoint.prism.impl.lex.LexicalProcessorRegistry; import com.evolveum.midpoint.prism.impl.lex.dom.DomLexicalProcessor; +import com.evolveum.midpoint.prism.metadata.ValueMetadataMockUpFactory; import com.evolveum.midpoint.prism.path.*; import com.evolveum.midpoint.prism.polystring.PolyStringNormalizer; import com.evolveum.midpoint.prism.impl.polystring.AlphanumericPolyStringNormalizer; @@ -41,6 +42,7 @@ import com.evolveum.midpoint.prism.impl.xnode.XNodeFactoryImpl; import com.evolveum.midpoint.prism.xnode.XNodeMutator; import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; @@ -87,6 +89,9 @@ public final class PrismContextImpl implements PrismContext { @NotNull private final ItemPathParser itemPathParser; @NotNull private final SchemaFactory schemaFactory; + @Experimental // temporary + private ValueMetadataMockUpFactory valueMetadataMockUpFactory; + private ParsingMigrator parsingMigrator; private PrismMonitor monitor = null; @@ -665,4 +670,14 @@ public void setExtraValidation(boolean value) { public SchemaFactory schemaFactory() { return schemaFactory; } + + @Override + public void setValueMetadataMockUpFactory(ValueMetadataMockUpFactory factory) { + this.valueMetadataMockUpFactory = factory; + } + + @Override + public ValueMetadataMockUpFactory getValueMetadataMockUpFactory() { + return valueMetadataMockUpFactory; + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismPropertyValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismPropertyValueImpl.java index 154cb24c0d7..cb60f3f138c 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismPropertyValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismPropertyValueImpl.java @@ -46,6 +46,7 @@ import java.io.Serializable; import java.util.Arrays; import java.util.Objects; +import java.util.Optional; /** * @author lazyman diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java index 92d04fd1e5f..3bab4c0b392 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java @@ -30,6 +30,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; /** diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismValueImpl.java index 9077a57c912..fc1efe67522 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismValueImpl.java @@ -10,6 +10,7 @@ import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.equivalence.ParameterizedEquivalenceStrategy; +import com.evolveum.midpoint.prism.metadata.ValueMetadataMockUpFactory; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.SchemaException; @@ -376,4 +377,16 @@ public Collection getAllValues(ItemPath path) { return emptySet(); } } + + @Override + public Optional valueMetadata() throws SchemaException { + PrismContext prismContext = getPrismContext(); + if (prismContext != null) { + ValueMetadataMockUpFactory factory = prismContext.getValueMetadataMockUpFactory(); + if (factory != null) { + return factory.createValueMetadata(this); + } + } + return Optional.empty(); + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/metadata/ValueMetadataAdapter.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/metadata/ValueMetadataAdapter.java new file mode 100644 index 00000000000..4a836f280df --- /dev/null +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/metadata/ValueMetadataAdapter.java @@ -0,0 +1,579 @@ +/* + * 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.prism.impl.metadata; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.evolveum.midpoint.prism.CloneStrategy; +import com.evolveum.midpoint.prism.ComplexTypeDefinition; +import com.evolveum.midpoint.prism.ConsistencyCheckScope; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.Itemable; +import com.evolveum.midpoint.prism.Objectable; +import com.evolveum.midpoint.prism.OriginType; +import com.evolveum.midpoint.prism.PartiallyResolvedItem; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContainerable; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.ValueMetadata; +import com.evolveum.midpoint.prism.Visitor; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; +import com.evolveum.midpoint.prism.equivalence.ParameterizedEquivalenceStrategy; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.SchemaException; + +@Experimental +public class ValueMetadataAdapter implements ValueMetadata { + + private final PrismContainerValue delegate; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private ValueMetadataAdapter(PrismContainerValue delegate) { + this.delegate = delegate; + } + + public static ValueMetadata holding(PrismContainerValue value) { + return new ValueMetadataAdapter(value); + } + + public PrismContext getPrismContext() { + return delegate.getPrismContext(); + } + + public void setOriginObject(Objectable source) { + delegate.setOriginObject(source); + } + + public void setOriginType(OriginType type) { + delegate.setOriginType(type); + } + + public boolean isImmutable() { + return delegate.isImmutable(); + } + + public OriginType getOriginType() { + return delegate.getOriginType(); + } + + public void freeze() { + delegate.freeze(); + } + + public void checkMutable() { + delegate.checkMutable(); + } + + public String debugDump() { + return delegate.debugDump(); + } + + public Objectable getOriginObject() { + return delegate.getOriginObject(); + } + + public void revive(PrismContext prismContext) throws SchemaException { + delegate.revive(prismContext); + } + + public void checkImmutable() { + delegate.checkImmutable(); + } + + public String debugDump(int indent) { + return delegate.debugDump(indent); + } + + public Object debugDumpLazily() { + return delegate.debugDumpLazily(); + } + + public Object debugDumpLazily(int indent) { + return delegate.debugDumpLazily(indent); + } + + public PrismContext getPrismContextLocal() { + return delegate.getPrismContextLocal(); + } + + public void setPrismContext(PrismContext prismContext) { + delegate.setPrismContext(prismContext); + } + + public @NotNull Collection> getItems() { + return delegate.getItems(); + } + + public Map getUserData() { + return delegate.getUserData(); + } + + public Object getUserData(@NotNull String key) { + return delegate.getUserData(key); + } + + public void setUserData(@NotNull String key, Object value) { + delegate.setUserData(key, value); + } + + public void setParent(Itemable parent) { + delegate.setParent(parent); + } + + public Optional valueMetadata() { + return Optional.of(this); + } + + public @NotNull ItemPath getPath() { + return delegate.getPath(); + } + + public void clearParent() { + delegate.clearParent(); + } + + public int size() { + return delegate.size(); + } + + public @NotNull Set> getProperties() { + return delegate.getProperties(); + } + + public void applyDefinition(ItemDefinition definition) throws SchemaException { + delegate.applyDefinition(definition); + } + + public void recompute() { + delegate.recompute(); + } + + public Long getId() { + return delegate.getId(); + } + + public void setId(Long id) { + delegate.setId(id); + } + + public PrismContainerable getParent() { + return delegate.getParent(); + } + + public PrismContainer getContainer() { + return delegate.getContainer(); + } + + public Containerable getValue() { + return delegate.getValue(); + } + + public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw, + ConsistencyCheckScope scope) { + delegate.checkConsistenceInternal(rootItem, requireDefinitions, prohibitRaw, scope); + } + + public @NotNull Containerable asContainerable() { + return delegate.asContainerable(); + } + + public Class getCompileTimeClass() { + return delegate.getCompileTimeClass(); + } + + public boolean canRepresent(Class clazz) { + return delegate.canRepresent(clazz); + } + + public Containerable asContainerable(Class requiredClass) { + return delegate.asContainerable(requiredClass); + } + + public boolean representsSameValue(PrismValue other, boolean lax) { + return delegate.representsSameValue(other, lax); + } + + public @NotNull Collection getItemNames() { + return delegate.getItemNames(); + } + + public void add(Item item) throws SchemaException { + delegate.add(item); + } + + public void add(Item item, boolean checkUniqueness) + throws SchemaException { + delegate.add(item, checkUniqueness); + } + + public boolean merge(Item item) throws SchemaException { + return delegate.merge(item); + } + + public void normalize() { + delegate.normalize(); + } + + public boolean subtract(Item item) + throws SchemaException { + return delegate.subtract(item); + } + + public int hashCode(@NotNull EquivalenceStrategy equivalenceStrategy) { + return delegate.hashCode(equivalenceStrategy); + } + + public int hashCode(@NotNull ParameterizedEquivalenceStrategy equivalenceStrategy) { + return delegate.hashCode(equivalenceStrategy); + } + + public boolean equals(PrismValue otherValue, @NotNull EquivalenceStrategy strategy) { + return delegate.equals(otherValue, strategy); + } + + public void addReplaceExisting(Item item) + throws SchemaException { + delegate.addReplaceExisting(item); + } + + public boolean equals(PrismValue otherValue, @NotNull ParameterizedEquivalenceStrategy strategy) { + return delegate.equals(otherValue, strategy); + } + + public boolean equals(PrismValue thisValue, PrismValue otherValue) { + return delegate.equals(thisValue, otherValue); + } + + public Collection diff(PrismValue otherValue) { + return delegate.diff(otherValue); + } + + public void remove(Item item) { + delegate.remove(item); + } + + public void removeAll() { + delegate.removeAll(); + } + + public void addAll(Collection> itemsToAdd) throws SchemaException { + delegate.addAll(itemsToAdd); + } + + public Collection diff(PrismValue otherValue, ParameterizedEquivalenceStrategy strategy) { + return delegate.diff(otherValue, strategy); + } + + public void addAllReplaceExisting(Collection> itemsToAdd) throws SchemaException { + delegate.addAllReplaceExisting(itemsToAdd); + } + + public @Nullable Class getRealClass() { + return delegate.getRealClass(); + } + + public boolean hasRealClass() { + return delegate.hasRealClass(); + } + + public void replace(Item oldItem, Item newItem) + throws SchemaException { + delegate.replace(oldItem, newItem); + } + + public @Nullable T getRealValue() { + return delegate.getRealValue(); + } + + public @Nullable Object getRealValueOrRawType(PrismContext prismContext) { + return delegate.getRealValueOrRawType(prismContext); + } + + public void clear() { + delegate.clear(); + } + + public boolean contains(Item item) { + return delegate.contains(item); + } + + public boolean contains(ItemName itemName) { + return delegate.contains(itemName); + } + + public PartiallyResolvedItem findPartial(ItemPath path) { + return delegate.findPartial(path); + } + + public PrismContainerValue getParentContainerValue() { + return delegate.getParentContainerValue(); + } + + public PrismProperty findProperty(ItemPath propertyPath) { + return delegate.findProperty(propertyPath); + } + + public QName getTypeName() { + return delegate.getTypeName(); + } + + public @NotNull Collection getAllValues(ItemPath path) { + return delegate.getAllValues(path); + } + + public PrismProperty findProperty(PrismPropertyDefinition propertyDefinition) { + return delegate.findProperty(propertyDefinition); + } + + public boolean isRaw() { + return delegate.isRaw(); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public String toHumanReadableString() { + return delegate.toHumanReadableString(); + } + + public Object find(ItemPath path) { + return delegate.find(path); + } + + public PrismContainer findContainer(QName containerName) { + return delegate.findContainer(containerName); + } + + public PrismReference findReference(QName elementName) { + return delegate.findReference(elementName); + } + + public PrismReference findReferenceByCompositeObjectElementName(QName elementName) { + return delegate.findReferenceByCompositeObjectElementName(elementName); + } + + public > I findItem(ItemPath itemName, + Class type) { + return delegate.findItem(itemName, type); + } + + public Item findItem(ItemPath itemPath) { + return delegate.findItem(itemPath); + } + + public > I findItem( + ItemDefinition itemDefinition, Class type) { + return delegate.findItem(itemDefinition, type); + } + + public boolean containsItem(ItemPath propPath, boolean acceptEmptyItem) throws SchemaException { + return delegate.containsItem(propPath, acceptEmptyItem); + } + + public > I createDetachedSubItem( + QName name, Class type, ID itemDefinition, boolean immutable) throws SchemaException { + return delegate.createDetachedSubItem(name, type, itemDefinition, immutable); + } + + public PrismContainer findOrCreateContainer(QName containerName) + throws SchemaException { + return delegate.findOrCreateContainer(containerName); + } + + public PrismReference findOrCreateReference(QName referenceName) throws SchemaException { + return delegate.findOrCreateReference(referenceName); + } + + public Item findOrCreateItem(QName containerName) + throws SchemaException { + return delegate.findOrCreateItem(containerName); + } + + public > I findOrCreateItem( + QName containerName, Class type) throws SchemaException { + return delegate.findOrCreateItem(containerName, type); + } + + public > I findOrCreateItem(ItemPath path, + Class type, ID definition) throws SchemaException { + return delegate.findOrCreateItem(path, type, definition); + } + + public PrismProperty findOrCreateProperty(ItemPath propertyPath) throws SchemaException { + return delegate.findOrCreateProperty(propertyPath); + } + + public PrismProperty findOrCreateProperty(PrismPropertyDefinition propertyDef) throws SchemaException { + return delegate.findOrCreateProperty(propertyDef); + } + + public PrismProperty createProperty(QName propertyName) throws SchemaException { + return delegate.createProperty(propertyName); + } + + public PrismProperty createProperty(PrismPropertyDefinition propertyDefinition) throws SchemaException { + return delegate.createProperty(propertyDefinition); + } + + public void removeProperty(ItemPath path) { + delegate.removeProperty(path); + } + + public void removeContainer(ItemPath path) { + delegate.removeContainer(path); + } + + public void removeReference(ItemPath path) { + delegate.removeReference(path); + } + + public void setPropertyRealValue(QName propertyName, T realValue, PrismContext prismContext) + throws SchemaException { + delegate.setPropertyRealValue(propertyName, realValue, prismContext); + } + + public T getPropertyRealValue(QName propertyName, Class type) { + return delegate.getPropertyRealValue(propertyName, type); + } + + public void recompute(PrismContext prismContext) { + delegate.recompute(prismContext); + } + + public void accept(Visitor visitor) { + delegate.accept(visitor); + } + + public void accept(Visitor visitor, ItemPath path, boolean recursive) { + delegate.accept(visitor, path, recursive); + } + + public boolean hasCompleteDefinition() { + return delegate.hasCompleteDefinition(); + } + + public boolean addRawElement(Object element) throws SchemaException { + return delegate.addRawElement(element); + } + + public boolean deleteRawElement(Object element) throws SchemaException { + return delegate.deleteRawElement(element); + } + + public boolean removeRawElement(Object element) { + return delegate.removeRawElement(element); + } + + public void applyDefinition(ItemDefinition definition, boolean force) throws SchemaException { + delegate.applyDefinition(definition, force); + } + + public void applyDefinition(@NotNull PrismContainerDefinition containerDef, boolean force) + throws SchemaException { + delegate.applyDefinition(containerDef, force); + } + + public boolean isIdOnly() { + return delegate.isIdOnly(); + } + + public void assertDefinitions(String sourceDescription) throws SchemaException { + delegate.assertDefinitions(sourceDescription); + } + + public void assertDefinitions(boolean tolerateRaw, String sourceDescription) throws SchemaException { + delegate.assertDefinitions(tolerateRaw, sourceDescription); + } + + public PrismContainerValue clone() { + return delegate.clone(); + } + + public PrismContainerValue createImmutableClone() { + return delegate.createImmutableClone(); + } + + public PrismContainerValue cloneComplex(CloneStrategy strategy) { + return delegate.cloneComplex(strategy); + } + + public boolean equivalent(PrismContainerValue other) { + return delegate.equivalent(other); + } + + public @Nullable ComplexTypeDefinition getComplexTypeDefinition() { + return delegate.getComplexTypeDefinition(); + } + + public PrismContainer asSingleValuedContainer(@NotNull QName itemName) throws SchemaException { + return delegate.asSingleValuedContainer(itemName); + } + + public void mergeContent(@NotNull PrismContainerValue other, @NotNull List overwrite) + throws SchemaException { + delegate.mergeContent(other, overwrite); + } + + public PrismContainerValue getRootValue() { + return delegate.getRootValue(); + } + + public void setOriginTypeRecursive(OriginType originType) { + delegate.setOriginTypeRecursive(originType); + } + + public void keepPaths(List keep) throws SchemaException { + delegate.keepPaths(keep); + } + + public void removePaths(List remove) throws SchemaException { + delegate.removePaths(remove); + } + + public void removeItems(List itemsToRemove) { + delegate.removeItems(itemsToRemove); + } + + public void removeOperationalItems() { + delegate.removeOperationalItems(); + } + + public PrismContainerDefinition getDefinition() { + return delegate.getDefinition(); + } + + public void acceptParentVisitor(Visitor visitor) { + delegate.acceptParentVisitor(visitor); + } + + public boolean hasNoItems() { + return delegate.hasNoItems(); + } + +} diff --git a/infra/prism-impl/src/main/resources/xml/ns/public/annotation-3.xsd b/infra/prism-impl/src/main/resources/xml/ns/public/annotation-3.xsd index a90acaaf556..d9b4d20787d 100644 --- a/infra/prism-impl/src/main/resources/xml/ns/public/annotation-3.xsd +++ b/infra/prism-impl/src/main/resources/xml/ns/public/annotation-3.xsd @@ -662,6 +662,17 @@ + + + + + EXPERIMENTAL: Value Metadata marker. This annotation marks elements to be considered as value metadata. + + + + + + diff --git a/infra/schema-pure-jaxb/src/compile/resources/catalog.xml b/infra/schema-pure-jaxb/src/compile/resources/catalog.xml index 296399de122..09a3d0618d4 100644 --- a/infra/schema-pure-jaxb/src/compile/resources/catalog.xml +++ b/infra/schema-pure-jaxb/src/compile/resources/catalog.xml @@ -28,6 +28,7 @@ Used when building via xjc. + diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidPointPrismContextFactory.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidPointPrismContextFactory.java index b1313b6c608..a9e66808c32 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidPointPrismContextFactory.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidPointPrismContextFactory.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.metadata.MidpointValueMetadataMockUpFactory; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.model.model_3.ObjectFactory; import org.jetbrains.annotations.NotNull; @@ -64,6 +65,7 @@ public PrismContext createPrismContext() throws SchemaException, FileNotFoundExc context.setMonitor(new InternalMonitor()); } context.setParsingMigrator(new MidpointParsingMigrator()); + context.setValueMetadataMockUpFactory(new MidpointValueMetadataMockUpFactory(context)); return context; } @@ -127,6 +129,9 @@ private void registerBuiltinSchemas(SchemaRegistryImpl schemaRegistry) throws Sc schemaRegistry.registerPrismDefaultSchemaResource("xml/ns/public/common/common-3.xsd", "c", com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectFactory.class.getPackage()); // declared by default + schemaRegistry.registerPrismSchemaResource("xml/ns/public/common/extension-metadata-3.xsd", "meta-ext"); + schemaRegistry.registerPrismSchemaResource("xml/ns/public/common/extension-metadata-mock-3.xsd", "meta-mock-ext"); + schemaRegistry.registerPrismSchemaResource("xml/ns/public/common/audit-3.xsd", "aud", com.evolveum.midpoint.xml.ns._public.common.audit_3.ObjectFactory.class.getPackage()); diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/metadata/MidpointValueMetadataMockUpFactory.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/metadata/MidpointValueMetadataMockUpFactory.java new file mode 100644 index 00000000000..c98c32e0d48 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/metadata/MidpointValueMetadataMockUpFactory.java @@ -0,0 +1,226 @@ +/* + * 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.schema.metadata; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.impl.metadata.ValueMetadataAdapter; +import com.evolveum.midpoint.prism.metadata.ValueMetadataMockUpFactory; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.util.annotation.Experimental; +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.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; + +/** + * TEMPORARY. WILL BE REMOVED AFTER PROTOTYPING IS OVER. + */ +@Experimental +public class MidpointValueMetadataMockUpFactory implements ValueMetadataMockUpFactory { + + private static final Trace LOGGER = TraceManager.getTrace(MidpointValueMetadataMockUpFactory.class); + + private static final String NS_MOCKUP = "http://midpoint.evolveum.com/xml/ns/public/common/extension-metadata-mockup-3"; + private static final ItemName ATTACHED_VALUE_METADATA_NAME = new ItemName(NS_MOCKUP, "attachedValueMetadata"); + + private static final ItemName ATTACHED_PATH = new ItemName(NS_MOCKUP, "path"); + private static final ItemName ATTACHED_VALUE = new ItemName(NS_MOCKUP, "value"); + private static final ItemName ATTACHED_METADATA = new ItemName(NS_MOCKUP, "metadata"); + private static final ItemName ATTACHED_SKIP_LEGACY_METADATA = new ItemName(NS_MOCKUP, "skipLegacyMetadata"); + + @NotNull private final PrismContext prismContext; + + public MidpointValueMetadataMockUpFactory(@NotNull PrismContext prismContext) { + this.prismContext = prismContext; + } + + @Override + public Optional createValueMetadata(@NotNull PrismValue value) throws SchemaException { + PrismObject object = getOwningObject(value); + if (object != null) { + ValueMetadataType metadata = createValueMetadata(object, value); + return metadata != null ? + Optional.of(ValueMetadataAdapter.holding(metadata.asPrismContainerValue())) : Optional.empty(); + } else { + LOGGER.warn("No owning object for {}, no metadata can be provided", value); + return Optional.empty(); + } + } + + private PrismObject getOwningObject(PrismValue value) { + Itemable parent = value.getParent(); + if (parent instanceof PrismObject) { + return ((PrismObject) parent); + } else if (parent instanceof Item) { + return getOwningObject(((Item) parent)); + } else { + return null; + } + } + + private PrismObject getOwningObject(Item item) { + PrismContainerValue parent = item.getParent(); + if (parent != null) { + return getOwningObject(parent); + } else { + return null; + } + } + + private ValueMetadataType createValueMetadata(PrismObject object, PrismValue value) throws SchemaException { + MetadataSources sources = findMetadataSources(object, value); + if (sources.attached == null && sources.legacy == null) { + return null; + } else { + ValueMetadataType aggregated; + if (sources.attached != null) { + aggregated = sources.attached.clone(); + } else { + aggregated = new ValueMetadataType(prismContext); + } + + if (sources.legacy != null) { + implantLegacy(aggregated, sources.legacy); + } + return aggregated; + } + } + + private void implantLegacy(ValueMetadataType aggregated, MetadataType legacy) throws SchemaException { + implant(aggregated, legacy, MetadataType.F_CREATE_TIMESTAMP, ValueMetadataType.F_STORAGE, StorageMetadataType.F_CREATE_TIMESTAMP); + implant(aggregated, legacy, MetadataType.F_CREATOR_REF, ValueMetadataType.F_STORAGE, StorageMetadataType.F_CREATOR_REF); + implant(aggregated, legacy, MetadataType.F_CREATE_CHANNEL, ValueMetadataType.F_STORAGE, StorageMetadataType.F_CREATE_CHANNEL); + implant(aggregated, legacy, MetadataType.F_CREATE_TASK_REF, ValueMetadataType.F_STORAGE, StorageMetadataType.F_CREATE_TASK_REF); + + implant(aggregated, legacy, MetadataType.F_MODIFY_TIMESTAMP, ValueMetadataType.F_STORAGE, StorageMetadataType.F_MODIFY_TIMESTAMP); + implant(aggregated, legacy, MetadataType.F_MODIFIER_REF, ValueMetadataType.F_STORAGE, StorageMetadataType.F_MODIFIER_REF); + implant(aggregated, legacy, MetadataType.F_MODIFY_CHANNEL, ValueMetadataType.F_STORAGE, StorageMetadataType.F_MODIFY_CHANNEL); + implant(aggregated, legacy, MetadataType.F_MODIFY_TASK_REF, ValueMetadataType.F_STORAGE, StorageMetadataType.F_MODIFY_TASK_REF); + + implant(aggregated, legacy, MetadataType.F_REQUEST_TIMESTAMP, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_REQUEST_TIMESTAMP); + implant(aggregated, legacy, MetadataType.F_REQUESTOR_REF, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_REQUESTOR_REF); + implant(aggregated, legacy, MetadataType.F_REQUESTOR_COMMENT, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_REQUESTOR_COMMENT); + implant(aggregated, legacy, MetadataType.F_CREATE_APPROVER_REF, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CREATE_APPROVER_REF); + implant(aggregated, legacy, MetadataType.F_CREATE_APPROVAL_COMMENT, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CREATE_APPROVAL_COMMENT); + implant(aggregated, legacy, MetadataType.F_CREATE_APPROVAL_TIMESTAMP, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CREATE_APPROVAL_TIMESTAMP); + implant(aggregated, legacy, MetadataType.F_MODIFY_APPROVER_REF, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_MODIFY_APPROVER_REF); + implant(aggregated, legacy, MetadataType.F_MODIFY_APPROVAL_COMMENT, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_MODIFY_APPROVAL_COMMENT); + implant(aggregated, legacy, MetadataType.F_MODIFY_APPROVAL_TIMESTAMP, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_MODIFY_APPROVAL_TIMESTAMP); + implant(aggregated, legacy, MetadataType.F_CERTIFICATION_FINISHED_TIMESTAMP, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CERTIFICATION_FINISHED_TIMESTAMP); + implant(aggregated, legacy, MetadataType.F_CERTIFICATION_OUTCOME, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CERTIFICATION_OUTCOME); + implant(aggregated, legacy, MetadataType.F_CERTIFIER_REF, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CERTIFIER_REF); + implant(aggregated, legacy, MetadataType.F_CERTIFIER_COMMENT, ValueMetadataType.F_PROCESS, ProcessMetadataType.F_CERTIFIER_COMMENT); + + implant(aggregated, legacy, MetadataType.F_LAST_PROVISIONING_TIMESTAMP, ValueMetadataType.F_PROVISIONING, ProvisioningMetadataType.F_LAST_PROVISIONING_TIMESTAMP); + + // origin mapping name is ignored + } + + private void implant(ValueMetadataType aggregated, MetadataType legacy, ItemName legacyName, ItemName... aggregatePathSegments) + throws SchemaException { + Item legacyItem = legacy.asPrismContainerValue().findItem(legacyName); + if (legacyItem != null) { + ItemDelta delta = prismContext.deltaFor(ValueMetadataType.class) + .item(aggregatePathSegments) + .replace(CloneUtil.cloneCollectionMembers(legacyItem.getValues())) + .asItemDelta(); + delta.applyTo(aggregated.asPrismContainerValue()); + } + } + + private static class MetadataSources { + private final ValueMetadataType attached; + private final MetadataType legacy; + + private MetadataSources(ValueMetadataType attached, MetadataType legacy) { + this.attached = attached; + this.legacy = legacy; + } + } + + private MetadataSources findMetadataSources(PrismObject object, PrismValue value) { + ItemPath path = value.getPath(); // will succeed as we know we can step up to the root object + System.out.println("Deriving value metadata for " + value + " in " + object + " (path = " + path + ")"); + + ValueMetadataType attached; + PrismContainerValue attachedInfo = findAttachedMetadata(object, path, value); + boolean skipLegacy; + if (attachedInfo != null) { + PrismContainer attachedMetadataContainer = attachedInfo.findContainer(ATTACHED_METADATA); + attached = attachedMetadataContainer != null ? attachedMetadataContainer.getRealValue() : null; + skipLegacy = Boolean.TRUE.equals(attachedInfo.getPropertyRealValue(ATTACHED_SKIP_LEGACY_METADATA, Boolean.class)); + } else { + attached = null; + skipLegacy = false; + } + + MetadataType legacy; + if (!skipLegacy && value instanceof PrismContainerValue) { + PrismContainer legacyMetadataContainer = + ((PrismContainerValue) value).findContainer(ObjectType.F_METADATA); + legacy = legacyMetadataContainer != null ? legacyMetadataContainer.getRealValue() : null; + } else { + legacy = null; + } + + return new MetadataSources(attached, legacy); + } + + // returns AttachedValueMetadataType or null + private PrismContainerValue findAttachedMetadata(PrismObject object, ItemPath path, PrismValue value) { + PrismContainer allAttached = object.findExtensionItem(ATTACHED_VALUE_METADATA_NAME); + if (allAttached != null) { + for (PrismContainerValue attached : allAttached.getValues()) { + if (matches(attached, path, value)) { + return attached; + } + } + } + return null; + } + + private boolean matches(PrismContainerValue attached, ItemPath path, PrismValue value) { + ItemPath attachedPath = getAttachedPath(attached); + if (path.equivalent(attachedPath)) { + PrismProperty attachedValueProperty = attached.findProperty(ATTACHED_VALUE); + return attachedValueProperty == null || attachedValueProperty.isEmpty() + || valueMatches(attachedValueProperty.getValue(), value); + } else { + return false; + } + } + + // Temporary implementation, expecting attached is always a String + private boolean valueMatches(PrismPropertyValue attached, PrismValue real) { + String expected = (String) attached.getRealValue(); + return real.getRealValue() != null && real.getRealValue().toString().equals(expected); + } + + @NotNull + private ItemPath getAttachedPath(PrismContainerValue attached) { + PrismProperty pathProperty = attached.findProperty(ATTACHED_PATH); + if (pathProperty != null) { + ItemPathType attachedPathValue = pathProperty.getRealValue(); + if (attachedPathValue != null) { + return attachedPathValue.getItemPath(); + } else { + return ItemPath.EMPTY_PATH; + } + } else { + return ItemPath.EMPTY_PATH; + } + } +} diff --git a/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml b/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml index 1aab1dec12e..3e902f7e45b 100644 --- a/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml +++ b/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml @@ -45,6 +45,10 @@ Notes: + + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd index 911a33df73b..b87df0b7088 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd @@ -96,5 +96,6 @@ + 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 8d071e08735..2c97e551018 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 @@ -83,9 +83,6 @@ white space may be skipped, multiple spaces can be collapsed to one and so on.

- - 4.1 - @@ -109,6 +106,9 @@ non-blank line of text is also skipped.

+ + 4.1 + @@ -867,6 +867,7 @@ MetadataType.requestTimestamp true 3.5 + @@ -879,6 +880,7 @@ MetadataType.requestorRef true tns:UserType + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-metadata-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-metadata-3.xsd new file mode 100644 index 00000000000..13592180ae1 --- /dev/null +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-metadata-3.xsd @@ -0,0 +1,583 @@ + + + + + + + + + Standard value metadata that is part of the static common-3 schema. + + + + + + + + + + + + + + + The value metadata. + + + + + + + + + + Place for deployment-specific value metadata. + + + + + + + The details when data were stored in midPoint repository. It is about the creation + (timestamp, originator user, channel, taskRef) and the last modification (the same). + + + + + + + The details how the data were processed by midPoint processes/tasks. + Only present if it was approved or otherwise processed by a "process". + + + + + + + TODO + + + + + + + TODO + + + + + + + + + + + + + true + + Storage metadata + + + + + + +

+ The timestamp of data creation. It is set once and should never be changed. +

+

+ In case of "background" processes to create object (e.g. create with approval) + this should be the timestamp when the process ended. I.e. the timestamp when + the operation was executed. +

+
+ + MetadataType.createTimestamp + true + true + 3.5 + +
+
+ + + + Reference to the user that created the data. + + + MetadataType.creatorRef + true + true + tns:UserType + + + + + + + Channel in which the object was created. + + + MetadataType.createChannel + true + true + + + + + + + Reference to the task that created the object (if it was a persistent one). + + + MetadataType.createTaskRef + true + tns:TaskType + 3.7 + + + + + + + The timestamp of last data modification. It should be updated to a current time + when the object is modified. + The modifications that change only operational attributes may not update the + modify timestamp. + + + MetadataType.modifyTimestamp + true + true + + + + + + + Reference to the user that modified the data. + + + MetadataType.modifierRef + true + true + tns:UserType + + + + + + + Channel in which the object was last modified. + + + MetadataType.modifyChannel + true + true + + + + + + + Reference to the task that last modified the object (if it was a persistent one). + If the last modification was carried out by synchronous task, this reference will be empty. + + + MetadataType.modifyTaskRef + true + tns:TaskType + 3.7 + + + +
+
+ + + + + + + true + + Process metadata + + + + + + +

+ The timestamp of "create" operation request. It is set once and should never be changed. +

+

+ In case of "background" processes to create object (e.g. create with approval) + this should be the timestamp when the process started. I.e. the timestamp when + the operation was requested. +

+
+ + MetadataType.requestTimestamp + true + 3.5 + + +
+
+ + + + Reference to the user that requested the "create" operation for this object or assignment. + + + MetadataType.requestorRef + true + tns:UserType + + + + + + + + Comment of the user that requested the "create" operation for this object or assignment. + + + MetadataType.requestorComment + true + 3.7 + + + + + + + Reference to the user that approved the creation of the data (if there was such a user). + This is multi-value reference therefore multiple approvers may be recorded. However the order and + hierarchy of the approvers is lost. + + + MetadataType.createApproverRef + true + true + tns:UserType + + + + + + + Comments of the approvers during the creation of the data. Note that these comments are in no + particular order, so basically it is not known who entered which comment. + + + MetadataType.createApprovalComment + true + 3.7 + + + + + + + The timestamp of creation approval. + + + MetadataType.createApprovalTimestamp + true + 3.5 + + + + + + +

+ Reference to the user that approved the last modification of the data (if there was such a user). + This is multi-value reference therefore multiple approvers may be recorded. However the order and + hierarchy of the approvers is lost. +

+

+ Even though this is multi-value reference it will get overwritten after each approval. + The multiple values are used only if all the approvers are known at the same time, + e.g. if multi-level approval is evaluated at the same time. But generally this refers + only to the last approval event. +

+
+ + MetadataType.modifyApproverRef + true + true + tns:UserType + +
+
+ + + +

+ Comments of the approvers during the last modification of the data. Note that these comments are in no + particular order, so basically it is not known who entered which comment. +

+

+ Even though this is multi-value property it will get overwritten after each approval. +

+
+ + MetadataType.modifyApprovalComment + true + 3.7 + +
+
+ + + + The timestamp of last modification approval. + + + MetadataType.modifyApprovalTimestamp + true + 3.5 + + + + + + + When last certification related to this item was finished. + Only certifications that resulted in non-null outcome are taken into account. + + + MetadataType.certificationFinishedTimestamp + true + 3.7 + + + + + + + Outcome (URI) of the last certification. + Only certifications that resulted in non-null outcome are taken into account. + + + MetadataType.certificationOutcome + true + 3.7 + + + + + + + Reference to the user that certified the data. + Contrary to approver/modifierRef, this field is filled-in also when certifier denies the item status. + Only certifications that resulted in non-null outcome are taken into account. + + + MetadataType.certifierRef + true + tns:UserType + 3.7 + + + + + + +

+ Comments of the certifiers during the last certification of the data. Note that these comments are in no + particular order, so basically it is not known who entered which comment. +

+

+ Even though this is multi-value property it will get overwritten after each approval. +

+

+ Only certifications that resulted in non-null outcome are taken into account. +

+
+ + MetadataType.certifierComment + true + 3.7 + +
+
+
+
+ + + + + + + true + + Provisioning metadata + + + + + + +

+ The timestamp last provisioning operation that was based on this object. + E.g. the timestamp of last modification of any account based on the + data from the user. This value is only updated if there was any + real change in the resource. +

+

+ This meta-datum is used as an informational property that tells when + the data were last synchronized in outbound direction. But it has another + important role. It is used indirectly to trigger optimistic locking + conflicts that are used to detect a "clean" recompute (i.e. recompute + that is processing data without any outside interaction). +

+
+ + MetadataType.lastProvisioningTimestamp + true + 3.6.1 + +
+
+
+
+ + + + + + + true + + Transformation metadata + + + + + + + Source(s) of the value. + + + + + + + Transformer(s) that acted upon the value. + + + + + + + + + + Specifies a value source. + + + true + + + + + + + + Type of the source (resource, object template, assignment, user action, ...) + + + + + + + Name of the source (whatever that means). + + + + + + + A midPoint object representing the source of the value (resource object, object template, assignment holder, user, ...) + + + + + + + Specific information needed to identify the source e.g. within the midPoint object + (mapping name, assignment path, ...). + + + + + + + Where was the original value stored. + + + + + + + + + + Specifies agent that transformed the value. + + + true + + + + + + + + Type of the transformer. Currently the only supported one is "mapping". + + + + + + + Name of the transformer. Currently mapping name. + + + + + + + A midPoint object holding the transformer. + + + + + + + Specific information needed to identify the transformer e.g. within the midPoint object + (mapping name, assignment path, ...). + + + + + +
diff --git a/infra/schema/src/main/resources/xml/ns/public/common/extension-metadata-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/extension-metadata-3.xsd new file mode 100644 index 00000000000..8747c89493a --- /dev/null +++ b/infra/schema/src/main/resources/xml/ns/public/common/extension-metadata-3.xsd @@ -0,0 +1,93 @@ + + + + + + + + + Sample metadata extension schema. + + FOR TESTING PURPOSES ONLY. WILL BE REMOVED SOON. + + + + + + + + + + + + + + + + + + + + + + Assurance metadata: an example of deployment-specific metadata. + + + + + + + + + + + + true + + Assurance metadata + + + + + + + Level of assurance. + + + + + + + Source(s) of LoA. + + + + + + + Verification data, e.g. signature(s). + + + + + + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/extension-metadata-mock-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/extension-metadata-mock-3.xsd new file mode 100644 index 00000000000..4e3e5e55d63 --- /dev/null +++ b/infra/schema/src/main/resources/xml/ns/public/common/extension-metadata-mock-3.xsd @@ -0,0 +1,100 @@ + + + + + + + + + Object extension used to provide mockup of value metadata. + + The "attachedValueMetadata" extension item is used to derive mock-up metadata for all + relevant values in the object. + + FOR TESTING PURPOSES ONLY. WILL BE REMOVED SOON. + + + + + + + + + + + + + + + + + + + + + + + + + + Defines metadata attached to a specific value. + + + + + + + + + + Path to specific item value. For multivalued containers it should end with + container value id. For other values (properties, references) the value + should be specified by providing it to "value" item. + + + + + + + Specific value that should be provided with the metadata. + Due to prism serialization bug this works with string/polystring values only! + + + + + + + Attached metadata itself. + + + + + + + Whether to skip (not use) legacy metadata i.e. MetadataType. + + + + + + + + + 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 new file mode 100644 index 00000000000..37b4828776c --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestValueMetadata.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2010-2017 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.intest; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.Optional; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.ValueMetadata; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.TestResource; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * Tests the value metadata handling. Currently the only "handling" is creation of metadata mock-up. + */ +@ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestValueMetadata extends AbstractInitializedModelIntegrationTest { + + public static final File TEST_DIR = new File("src/test/resources/metadata"); + + private static final TestResource USER_ALICE = new TestResource<>(TEST_DIR, "user-alice.xml", "9fc389be-5b47-4e9d-90b5-33fffd87b3ca"); + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + addObject(USER_ALICE, initTask, initResult); + } + + @Test + public void test100CheckValueMetadata() throws Exception { + given(); + + when(); + PrismObject alice = getUser(USER_ALICE.oid); + + Optional objectMetadata = alice.getValue().valueMetadata(); + Optional nameMetadata = alice.findItem(UserType.F_NAME).getValue().valueMetadata(); + Optional givenNameMetadata = alice.findItem(UserType.F_GIVEN_NAME).getValue().valueMetadata(); + Optional familyNameMetadata = alice.findItem(UserType.F_FAMILY_NAME).getValue().valueMetadata(); + Optional fullNameMetadata = alice.findItem(UserType.F_FULL_NAME).getValue().valueMetadata(); + Optional developmentMetadata = alice.findProperty(UserType.F_ORGANIZATIONAL_UNIT) + .getAnyValue(ppv -> "Development".equals(PolyString.getOrig((PolyString) ppv.getRealValue()))) + .valueMetadata(); + //noinspection unchecked + PrismContainerValue assignment111 = (PrismContainerValue) alice.find(ItemPath.create(UserType.F_ASSIGNMENT, 111L)); + Optional assignmentMetadata = assignment111.valueMetadata(); + Optional manualSubtypeMetadata = assignment111.findProperty(AssignmentType.F_SUBTYPE) + .getAnyValue(ppv -> "manual".equals(ppv.getRealValue())) + .valueMetadata(); + Optional assignmentAdminStatusMetadata = + assignment111.findProperty(ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS)) + .getValue().valueMetadata(); + then(); + + display("alice after", alice); + + assertThat(objectMetadata).as("object metadata").isPresent(); + displayDumpable("object metadata", objectMetadata.get()); + assertThat(cast(objectMetadata).getProcess()).as("process metadata") + .isNotNull() + .extracting(ProcessMetadataType::getRequestTimestamp).as("request timestamp").isNotNull(); + + assertThat(nameMetadata).as("name metadata").isPresent(); + displayDumpable("name metadata", nameMetadata.get()); + assertThat(cast(nameMetadata).getTransformation()) + .as("name transformation metadata") + .isNotNull() + .extracting(TransformationMetadataType::getSource) + .asList().hasSize(1); + assertThat(cast(nameMetadata).getTransformation().getSource().get(0).getKind()) + .as("name transformation source kind") + .isEqualTo("http://midpoint.evolveum.com/data-provenance/source#resource"); + + assertThat(givenNameMetadata).as("given name metadata").isPresent(); + displayDumpable("given name metadata", givenNameMetadata.get()); + + assertThat(familyNameMetadata).as("family name metadata").isPresent(); + displayDumpable("family name metadata", familyNameMetadata.get()); + + assertThat(fullNameMetadata).as("full name metadata").isPresent(); + displayDumpable("full name metadata", fullNameMetadata.get()); + + assertThat(developmentMetadata).as("Development OU metadata").isPresent(); + displayDumpable("Development OU metadata", developmentMetadata.get()); + + assertThat(assignmentMetadata).as("assignment[111] metadata").isPresent(); + displayDumpable("assignment[111] metadata", assignmentMetadata.get()); + + assertThat(manualSubtypeMetadata).as("assignment[111] subtype of 'manual' metadata").isPresent(); + displayDumpable("assignment[111] subtype of 'manual' metadata", manualSubtypeMetadata.get()); + + assertThat(assignmentAdminStatusMetadata).as("assignment[111] admin status metadata").isPresent(); + displayDumpable("assignment[111] admin status metadata", assignmentAdminStatusMetadata.get()); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private ValueMetadataType cast(Optional metadata) { + //noinspection OptionalGetWithoutIsPresent + return (ValueMetadataType) (metadata.get().asContainerable()); + } +} diff --git a/model/model-intest/src/test/resources/metadata/user-alice.xml b/model/model-intest/src/test/resources/metadata/user-alice.xml new file mode 100644 index 00000000000..87eb5567fa3 --- /dev/null +++ b/model/model-intest/src/test/resources/metadata/user-alice.xml @@ -0,0 +1,232 @@ + + + + alice + + + + name + + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + name + + + + + http://example.com/identity/loa#official + + + + + + + givenName + + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + givenName + + + + + http://example.com/identity/loa#official + + + + + + familyName + + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + familyName + + + + + http://example.com/identity/loa#official + + + + + + honorificSuffix + + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + honorificSuffix + + + + + http://example.com/identity/loa#official + + + + + + nickName + + + + http://midpoint.evolveum.com/data-provenance/source#userAction + alice + + nickName + + + + + http://example.com/identity/loa#basic + + + + + + fullName + + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + givenName + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + familyName + + + http://midpoint.evolveum.com/data-provenance/source#resource + Dummy Resource + + + honorificSuffix + + + http://midpoint.evolveum.com/data-provenance/transformer#mapping + mapping-fullname + + + + + + http://example.com/identity/loa#official + + + + + + organizationalUnit + Development + + + + http://midpoint.evolveum.com/data-provenance/source#userAction + jack + + organizationalUnit + + + + + http://example.com/identity/loa#absolute + + + + + + assignment[111] + + + + http://example.com/identity/loa#none + + + + + http://midpoint.evolveum.com/data-provenance/source#userAction + willTurner + + + + + + + assignment[111]/subtype + manual + + + + http://midpoint.evolveum.com/data-provenance/source#userAction + willTurner + + + + + + + assignment[111]/activation/administrativeStatus + + + + Wait a moment, Alice! + + + + + + + experimental + manual + + + disabled + + + + autocratic + + + disabled + + + Alice + Green + Ph.D. + Alice Green, Ph.D. + Operations + Development +