diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/PrismQuerySerialization.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/PrismQuerySerialization.java new file mode 100644 index 00000000000..22e06046486 --- /dev/null +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/PrismQuerySerialization.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020-2021 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.query; + +import com.evolveum.midpoint.prism.PrismNamespaceContext; + +public interface PrismQuerySerialization { + + PrismNamespaceContext namespaceContext(); + + String filterText(); + + class NotSupportedException extends Exception { + + private static final long serialVersionUID = -5393426442630191647L; + + public NotSupportedException(String message, Throwable cause) { + super(message, cause); + } + + public NotSupportedException(String message) { + super(message); + } + } + +} diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/PrismQuerySerializer.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/PrismQuerySerializer.java new file mode 100644 index 00000000000..583fca5d840 --- /dev/null +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/PrismQuerySerializer.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020-2021 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.query; + +import java.util.Optional; + +import com.evolveum.midpoint.prism.PrismNamespaceContext; + +public interface PrismQuerySerializer { + + PrismQuerySerialization serialize(ObjectFilter filter, PrismNamespaceContext context) throws PrismQuerySerialization.NotSupportedException; + + + default PrismQuerySerialization serialize(ObjectFilter filter) throws PrismQuerySerialization.NotSupportedException { + return serialize(filter, PrismNamespaceContext.EMPTY); + } + + default Optional trySerialize(ObjectFilter filter) { + return trySerialize(filter, PrismNamespaceContext.EMPTY); + } + + default Optional trySerialize(ObjectFilter filter, PrismNamespaceContext namespaceContext) { + try { + return Optional.of(serialize(filter, namespaceContext)); + } catch (PrismQuerySerialization.NotSupportedException e) { + return Optional.empty(); + } + } + + +} diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterNames.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterNames.java new file mode 100644 index 00000000000..5b4d81d9e9b --- /dev/null +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterNames.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020-2021 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.query.lang; + +import java.util.Map; +import java.util.Optional; + +import javax.xml.namespace.QName; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; + +class FilterNames { + + public static final String QUERY_NS = "http://prism.evolveum.com/xml/ns/public/query-3"; + public static final String MATCHING_RULE_NS = "http://prism.evolveum.com/xml/ns/public/matching-rule-3"; + + public static final QName AND = queryName("and"); + public static final QName OR = queryName("or"); + public static final QName EQUAL = queryName("equal"); + public static final QName LESS = queryName("less"); + public static final QName GREATER = queryName("greater"); + public static final QName LESS_OR_EQUAL = queryName("lessOrEqual"); + public static final QName GREATER_OR_EQUAL = queryName("greaterOrEqual"); + public static final QName CONTAINS = queryName("contains"); + public static final QName STARTS_WITH = queryName("startsWith"); + public static final QName ENDS_WITH = queryName("endsWith"); + public static final QName MATCHES = queryName("matches"); + public static final QName EXISTS = queryName("exists"); + public static final QName FULL_TEXT = queryName("fullText"); + public static final QName IN_OID = queryName("inOid"); + public static final QName OWNED_BY_OID = queryName("ownedByOid"); + public static final QName IN_ORG = queryName("inOrg"); + public static final QName IS_ROOT = queryName("isRoot"); + public static final QName NOT = queryName("not"); + public static final QName NOT_EQUAL = queryName("notEqual"); + + static final BiMap ALIAS_TO_NAME = ImmutableBiMap.builder() + .put("=", EQUAL) + .put("<", LESS) + .put(">", GREATER) + .put("<=", LESS_OR_EQUAL) + .put(">=", GREATER_OR_EQUAL) + .put("!=", NOT_EQUAL) + .build(); + + static final Map NAME_TO_ALIAS = ALIAS_TO_NAME.inverse(); + + private FilterNames() { + throw new UnsupportedOperationException("Utility class"); + } + + private static QName queryName(String localName) { + return new QName(QUERY_NS, localName); + } + + static Optional fromAlias(String alias) { + return Optional.ofNullable(ALIAS_TO_NAME.get(alias)); + } + + static Optional aliasFor(QName name) { + return Optional.ofNullable(NAME_TO_ALIAS.get(name)); + } + +} diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterSerializer.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterSerializer.java new file mode 100644 index 00000000000..f44e742f763 --- /dev/null +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterSerializer.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020-2021 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.query.lang; + +import com.evolveum.midpoint.prism.query.PrismQuerySerialization; +import com.evolveum.midpoint.prism.query.ObjectFilter; + +interface FilterSerializer { + + @SuppressWarnings("unchecked") + default void castAndWrite(ObjectFilter source, QueryWriter target) throws PrismQuerySerialization.NotSupportedException { + write((T) source, target); + } + + void write(T source, QueryWriter target) throws PrismQuerySerialization.NotSupportedException; + +} diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterSerializers.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterSerializers.java new file mode 100644 index 00000000000..ab9144a53b0 --- /dev/null +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/FilterSerializers.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2020-2021 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.query.lang; + +import com.evolveum.midpoint.prism.impl.query.AllFilterImpl; +import com.evolveum.midpoint.prism.impl.query.AndFilterImpl; +import com.evolveum.midpoint.prism.impl.query.EqualFilterImpl; +import com.evolveum.midpoint.prism.impl.query.ExistsFilterImpl; +import com.evolveum.midpoint.prism.impl.query.FullTextFilterImpl; +import com.evolveum.midpoint.prism.impl.query.GreaterFilterImpl; +import com.evolveum.midpoint.prism.impl.query.InOidFilterImpl; +import com.evolveum.midpoint.prism.impl.query.LessFilterImpl; +import com.evolveum.midpoint.prism.impl.query.NoneFilterImpl; +import com.evolveum.midpoint.prism.impl.query.NotFilterImpl; +import com.evolveum.midpoint.prism.impl.query.OrFilterImpl; +import com.evolveum.midpoint.prism.impl.query.OrgFilterImpl; +import com.evolveum.midpoint.prism.impl.query.PropertyValueFilterImpl; +import com.evolveum.midpoint.prism.impl.query.RefFilterImpl; +import com.evolveum.midpoint.prism.impl.query.SubstringFilterImpl; +import com.evolveum.midpoint.prism.impl.query.TypeFilterImpl; +import com.evolveum.midpoint.prism.impl.query.UndefinedFilterImpl; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.query.AndFilter; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.OrFilter; +import com.evolveum.midpoint.prism.query.OrgFilter.Scope; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; + + +import static com.evolveum.midpoint.prism.query.PrismQuerySerialization.NotSupportedException; +import static com.evolveum.midpoint.prism.impl.query.lang.FilterNames.*; + + +import java.util.Map; +import java.util.AbstractMap.SimpleEntry; +import java.util.Iterator; +import java.util.Map.Entry; + +import javax.xml.namespace.QName; + +import org.jetbrains.annotations.Nullable; + +import com.evolveum.midpoint.prism.ExpressionWrapper; +import com.evolveum.midpoint.prism.PrismConstants; +import com.evolveum.midpoint.prism.PrismReferenceValue; + +public class FilterSerializers { + + private static final QName POLYSTRING_STRICT = PrismConstants.POLY_STRING_STRICT_MATCHING_RULE_NAME; + private static final QName POLYSTRING_ORIG = PrismConstants.POLY_STRING_ORIG_MATCHING_RULE_NAME; + + private static final Map, FilterSerializer> SERIALIZERS = ImmutableMap + ., FilterSerializer>builder() + .put(mapping(AllFilterImpl.class, FilterSerializers::allFilter)) + .put(mapping(ExistsFilterImpl.class, FilterSerializers::existsFilter)) + .put(mapping(FullTextFilterImpl.class, FilterSerializers::fullTextFilter)) + .put(mapping(InOidFilterImpl.class, FilterSerializers::inOidFilter)) + .put(mapping(AndFilterImpl.class, FilterSerializers::andFilter)) + .put(mapping(OrFilterImpl.class, FilterSerializers::orFilter)) + .put(mapping(NoneFilterImpl.class, FilterSerializers::noneFilter)) + .put(mapping(TypeFilterImpl.class, FilterSerializers::typeFilter)) + .put(mapping(UndefinedFilterImpl.class, FilterSerializers::undefinedFilter)) + .put(mapping(GreaterFilterImpl.class, FilterSerializers::greaterFilter)) + .put(mapping(LessFilterImpl.class, FilterSerializers::lessFilter)) + .put(mapping(EqualFilterImpl.class, FilterSerializers::equalFilter)) + .put(mapping(SubstringFilterImpl.class, FilterSerializers::substringFilter)) + .put(mapping(RefFilterImpl.class, FilterSerializers::refFilter)) + .put(mapping(NotFilterImpl.class, FilterSerializers::notFilter)) + .put(mapping(OrgFilterImpl.class, FilterSerializers::orgFilter)).build(); + + static void write(ObjectFilter filter, QueryWriter output) throws NotSupportedException { + FilterSerializer maybeSerializer = SERIALIZERS.get(filter.getClass()); + checkSupported(maybeSerializer != null, "Serialization of %s is not supported", filter.getClass()); + maybeSerializer.castAndWrite(filter, output); + } + + private static Entry, FilterSerializer> mapping(Class clazz, + FilterSerializer serializer) { + return new SimpleEntry<>(clazz, serializer); + } + + private static void checkSupported(boolean check, String template, Object... args) + throws NotSupportedException { + if (!check) { + throw new NotSupportedException(Strings.lenientFormat(template, args)); + } + } + + static void orgFilter(OrgFilterImpl source, QueryWriter target) throws NotSupportedException { + target.writeSelf(); + if (source.isRoot()) { + target.writeFilterName(IS_ROOT); + } else { + checkSupported(Scope.SUBTREE.equals(source.getScope()), "Only subtree scope is supported"); + PrismReferenceValue orgRef = source.getOrgRef(); + target.writeRawValue(orgRef.getOid()); + } + } + + static void notFilter(NotFilterImpl source, QueryWriter target) throws NotSupportedException { + ObjectFilter nested = source.getFilter(); + if (nested instanceof EqualFilterImpl) { + valueFilter(NOT_EQUAL, (EqualFilterImpl) nested, target); + } else { + target.writeNegatedFilter(source.getFilter()); + } + } + + static void allFilter(AllFilterImpl source, QueryWriter target) throws NotSupportedException { + checkSupported(false, "Filter AllFilterImpl Not Supported"); + } + + static void existsFilter(ExistsFilterImpl source, QueryWriter target) throws NotSupportedException { + target.writePath(source.getFullPath()); + + ObjectFilter nested = source.getFilter(); + if (nested != null) { + target.writeFilterName(MATCHES); + target.writeNestedFilter(nested); + } else { + target.writeFilterName(EXISTS); + } + } + + static void fullTextFilter(FullTextFilterImpl source, QueryWriter target) + throws NotSupportedException { + checkSupported(false, "Filter FullTextFilterImpl Not Supported"); + checkExpressionSupported(source.getExpression()); + target.writeSelf(); + target.writeFilterName(FULL_TEXT); + target.writeRawValues(source.getValues()); + + } + + static void inOidFilter(InOidFilterImpl source, QueryWriter target) throws NotSupportedException { + checkExpressionSupported(source.getExpression()); + target.writeSelf(); + target.writeFilterName(source.isConsiderOwner() ? OWNED_BY_OID : IN_OID); + target.writeRawValues(source.getOids()); + } + + static void andFilter(AndFilterImpl source, QueryWriter target) throws NotSupportedException { + Iterator conditions = source.getConditions().iterator(); + while (conditions.hasNext()) { + ObjectFilter condition = conditions.next(); + if (condition instanceof OrFilter) { + target.writeNestedFilter(condition); + } else { + target.writeFilter(condition); + } + if (conditions.hasNext()) { + target.writeFilterName(AND); + } + } + } + + static void orFilter(OrFilterImpl source, QueryWriter target) throws NotSupportedException { + Iterator conditions = source.getConditions().iterator(); + while (conditions.hasNext()) { + ObjectFilter condition = conditions.next(); + if (condition instanceof AndFilter) { + target.writeNestedFilter(condition); + } else { + target.writeFilter(condition); + } + if (conditions.hasNext()) { + target.writeFilterName(OR); + } + } + } + + static void noneFilter(NoneFilterImpl source, QueryWriter target) throws NotSupportedException { + checkSupported(false, "Filter NoneFilterImpl Not Supported"); + } + + static void typeFilter(TypeFilterImpl source, QueryWriter target) throws NotSupportedException { + checkSupported(false, "Filter TypeFilterImpl Not Supported"); + } + + static void undefinedFilter(UndefinedFilterImpl source, QueryWriter target) + throws NotSupportedException { + checkSupported(false, "Filter UndefinedFilterImpl Not Supported"); + } + + static void greaterFilter(GreaterFilterImpl source, QueryWriter target) + throws NotSupportedException { + valueFilter(source.isEquals() ? GREATER_OR_EQUAL : GREATER, source, target); + } + + static void lessFilter(LessFilterImpl source, QueryWriter target) throws NotSupportedException { + valueFilter(source.isEquals() ? LESS_OR_EQUAL : LESS, source, target); + } + + static void equalFilter(EqualFilterImpl source, QueryWriter target) throws NotSupportedException { + if (isNotExistsFilter(source)) { + target = target.negated(); + target.writePath(source.getFullPath()); + target.writeFilterName(EXISTS); + } else if (isPolystringMatchesFilter(source)) { + polystringMatchesFilter(source, target); + } else { + valueFilter(EQUAL, source, target); + } + } + + private static void polystringMatchesFilter(EqualFilterImpl source, QueryWriter target) { + var poly = (PolyString) source.getValues().get(0).getValue(); + QName matchingRule = source.getMatchingRule(); + target.writePath(source.getFullPath()); + target.writeFilterName(MATCHES); + target.startNestedFilter(); + if (POLYSTRING_ORIG.equals(matchingRule)) { + writeProperty(target, "orig", poly.getOrig(), false, false); + } else if (POLYSTRING_STRICT.equals(matchingRule)) { + writeProperty(target, "orig", poly.getOrig(), false, false); + writeProperty(target, "norm", poly.getNorm(), false, true); + } else { // also POLYSTRING_NORM + writeProperty(target, "norm", poly.getNorm(), false, false); + } + target.endNestedFilter(); + } + + private static boolean isPolystringMatchesFilter(EqualFilterImpl source) { + return source.getValues().size() == 1 && source.getValues().get(0).getRealValue() instanceof PolyString; + } + + private static boolean isNotExistsFilter(EqualFilterImpl source) { + return source.getRightHandSidePath() == null && (source.getValues() == null || source.getValues().isEmpty()); + } + + static void substringFilter(SubstringFilterImpl source, QueryWriter target) + throws NotSupportedException { + final QName name; + if (source.isAnchorStart() && source.isAnchorEnd()) { + name = EQUAL; + } else if (source.isAnchorStart()) { + name = STARTS_WITH; + } else if (source.isAnchorEnd()) { + name = ENDS_WITH; + } else { + name = CONTAINS; + } + valueFilter(name, source, target); + } + + static void refFilter(RefFilterImpl source, QueryWriter target) throws NotSupportedException { + checkSupported(source.getValues().size() == 1, "Only one reference is supported"); + checkExpressionSupported(source.getExpression()); + target.writePath(source.getFullPath()); + target.writeFilterName(MATCHES); + target.startNestedFilter(); + for (PrismReferenceValue value : source.getValues()) { + var oidEmitted = writeProperty(target, "oid", value.getOid(), source.isOidNullAsAny(), false); + var targetEmitted = writeProperty(target, "type", value.getTargetType(), source.isTargetTypeNullAsAny(), + oidEmitted); + writeProperty(target, "relation", value.getRelation(), true, targetEmitted); + } + target.endNestedFilter(); + } + + private static void checkExpressionSupported(@Nullable ExpressionWrapper expression) throws NotSupportedException { + checkSupported(expression == null, "Expression serialization not supported yet"); + } + + private static boolean writeProperty(QueryWriter target, String path, Object value, boolean skipNull, + boolean emitAnd) { + if (skipNull && value == null) { + return false; + } + if (emitAnd) { + target.writeFilterName(AND); + } + target.writePath(path); + target.writeFilterName(EQUAL); + target.writeRawValue(value); + return true; + } + + private static void valueFilter(QName name, PropertyValueFilterImpl source, QueryWriter target) throws NotSupportedException { + checkExpressionSupported(source.getExpression()); + target.writePath(source.getFullPath()); + target.writeFilterName(name); + target.writeMatchingRule(source.getMatchingRule()); + + @Nullable + ItemPath right = source.getRightHandSidePath(); + if(right != null) { + target.writePath(right); + } else { + target.writeValues(source.getValues()); + } + } + +} diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java new file mode 100644 index 00000000000..ed20a9a67ec --- /dev/null +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020-2021 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.query.lang; + +import java.util.Map; + +import com.evolveum.axiom.concepts.Builder; +import com.evolveum.midpoint.prism.PrismNamespaceContext; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.PrismQuerySerialization; +import com.evolveum.midpoint.prism.query.PrismQuerySerialization.NotSupportedException; +import com.evolveum.midpoint.prism.query.PrismQuerySerializer; + +public class PrismQuerySerializerImpl implements PrismQuerySerializer { + + @Override + public PrismQuerySerialization serialize(ObjectFilter filter, PrismNamespaceContext context) + throws NotSupportedException { + QueryWriter output = new QueryWriter(new SimpleBuilder(context)); + output.writeFilter(filter); + return output.build(); + } + + static class Result implements PrismQuerySerialization { + + private final PrismNamespaceContext prefixes; + private final String query; + + public Result(PrismNamespaceContext prefixes, String query) { + this.prefixes = prefixes; + this.query = query; + } + + @Override + public String filterText() { + return query; + } + + @Override + public PrismNamespaceContext namespaceContext() { + return prefixes; + } + + + } + + + static class SimpleBuilder implements Builder { + + private final PrismNamespaceContext.Builder prefixes; + private final StringBuilder query = new StringBuilder(); + private boolean spaceRequired = false; + + public SimpleBuilder(PrismNamespaceContext context) { + this.prefixes = context.childBuilder(); + } + + public void emitWord(String pathSelf) { + emitSpace(); + emit(pathSelf); + } + + public String filter() { + return query.toString(); + } + + public PrismNamespaceContext context() { + return prefixes.build(); + } + + public void addPrefixes(Map undeclaredPrefixes) { + prefixes.addPrefixes(undeclaredPrefixes); + } + + public void emitSpace() { + if(spaceRequired) { + emitSeparator(" "); + } + } + + public void emitSeparator(String string) { + query.append(string); + spaceRequired = false; + } + + public void emit(String prefix) { + query.append(prefix); + spaceRequired = true; + } + + public String prefixFor(String namespaceURI) { + return prefixes.assignPrefixFor(namespaceURI); + } + + @Override + public Result build() { + return new Result(prefixes.build(), query.toString()); + } + } +} diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java new file mode 100644 index 00000000000..5e3eaa57f82 --- /dev/null +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2020-2021 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.query.lang; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +import javax.xml.namespace.QName; + +import org.jetbrains.annotations.Nullable; + +import com.evolveum.axiom.concepts.Builder; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.impl.marshaller.ItemPathSerialization; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.UniformItemPath; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.PrismQuerySerialization; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import static com.evolveum.midpoint.prism.query.PrismQuerySerialization.NotSupportedException; + + +public class QueryWriter implements Builder { + + private static final String PATH_SELF = "."; + + private static final String MATCHING_RULE_NS = PrismQueryLanguageParserImpl.MATCHING_RULE_NS; + + private final PrismQuerySerializerImpl.SimpleBuilder target; + + public QueryWriter(PrismQuerySerializerImpl.SimpleBuilder target) { + this.target = target; + } + + public void writeSelf() { + target.emitWord(PATH_SELF); + } + + + void writePath(String string) { + target.emitSpace(); + target.emit(string); + } + + + public void writePath(ItemPath path) { + ItemPathSerialization pathSer = ItemPathSerialization.serialize(UniformItemPath.from(path), target.context()); + target.addPrefixes(pathSer.undeclaredPrefixes()); + target.emitSpace(); + target.emit(pathSer.getXPathWithoutDeclarations()); + } + + public void writeMatchingRule(@Nullable QName matchingRule) { + if(matchingRule == null) { + return; + } + target.emit("["); + emitQName(matchingRule, MATCHING_RULE_NS); + + target.emit("]"); + + } + + public void writeFilterName(QName filter) { + var alias = lookupAlias(filter); + target.emitSpace(); + if (alias.isPresent()) { + target.emit(alias.get()); + } else { + emitQName(filter, FilterNames.QUERY_NS); + } + } + + public void writeFilter(ObjectFilter filter) throws NotSupportedException { + writeFilter(filter, this); + } + + public void writeNestedFilter(ObjectFilter condition) throws NotSupportedException { + startNestedFilter(); + writeFilter(condition); + endNestedFilter(); + } + + public void writeNegatedFilter(ObjectFilter filter) throws NotSupportedException { + writeFilter(filter, negated()); + } + + public void writeValues(@Nullable List> values) { + Preconditions.checkArgument(values != null && !values.isEmpty(), "Value must be specified"); + writeList(values, this::writeValue); + } + + + public void startNestedFilter() { + target.emitSpace(); + target.emitSeparator("("); + } + + public void endNestedFilter() { + target.emit(")"); + } + + public void writeRawValue(Object rawValue) { + target.emitSpace(); + if (rawValue instanceof ItemPath) { + writePath((ItemPath) rawValue); + return; + } + if (rawValue instanceof QName) { + writeQName((QName) rawValue); + return; + } + if (rawValue instanceof Number) { + // FIXME: we should have some common serialization utility + target.emit(rawValue.toString()); + return; + } + writeString(rawValue); + } + + public void writeRawValues(Collection oids) { + writeList(oids, this::writeRawValue); + } + + @Override + public PrismQuerySerialization build() { + return target.build(); + } + + private Optional lookupAlias(QName filter) { + return FilterNames.aliasFor(filter); + } + + private void emitQName(QName filter, String additionalDefaultNs) { + String prefix = resolvePrefix(filter, additionalDefaultNs); + if(prefix != null) { + target.emit(prefix); + target.emit(":"); + } + target.emit(filter.getLocalPart()); + } + + private String resolvePrefix(QName name, String additionalDefaultNs) { + if (Strings.isNullOrEmpty(name.getNamespaceURI()) || Objects.equals(additionalDefaultNs, name.getNamespaceURI())) { + return null; + } + return target.prefixFor(name.getNamespaceURI()); + } + + + private void writeFilter(ObjectFilter filter, QueryWriter output) throws NotSupportedException { + FilterSerializers.write(filter, output); + } + + QueryWriter negated() { + return new Negated(target); + } + + private void writeList(Collection values, Consumer writer) { + target.emitSpace(); + if(values.size() == 1) { + writer.accept(values.iterator().next()); + } else { + target.emitSeparator("("); + var valuesIter = values.iterator(); + while (valuesIter.hasNext()) { + writer.accept(valuesIter.next()); + if (valuesIter.hasNext()) { + target.emitSeparator(", "); + } + } + target.emit(")"); + } + } + + private void writeValue(PrismPropertyValue prismPropertyValue) { + // Now we emit values + Object rawValue = prismPropertyValue.getValue(); + //QName typeName = prismPropertyValue.getTypeName(); + + writeRawValue(rawValue); + } + + private void writeString(Object rawValue) { + // FIXME: Add escapers and String Type detection + target.emit("'"); + target.emit(rawValue.toString()); + + target.emit("'"); + } + + private void writeQName(QName rawValue) { + emitQName(rawValue, null); + } + + class Negated extends QueryWriter { + + public Negated(PrismQuerySerializerImpl.SimpleBuilder target) { + super(target); + } + + @Override + public void writeFilterName(QName filter) { + target.emitWord("not"); + super.writeFilterName(filter); + } + + @Override + public void writeFilter(ObjectFilter filter) throws NotSupportedException { + super.writeFilter(filter, QueryWriter.this); + } + + @Override + QueryWriter negated() { + return this; + } + + } + + +} diff --git a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/query/lang/TestBasicQueryConversions.java b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/query/lang/TestBasicQueryConversions.java index 9bf53a28802..902232d8e6c 100644 --- a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/query/lang/TestBasicQueryConversions.java +++ b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/query/lang/TestBasicQueryConversions.java @@ -25,6 +25,9 @@ import com.evolveum.midpoint.prism.foo.UserType; import com.evolveum.midpoint.prism.impl.match.MatchingRuleRegistryFactory; import com.evolveum.midpoint.prism.impl.query.FullTextFilterImpl; +import com.evolveum.midpoint.prism.impl.query.lang.PrismQuerySerializerImpl; + +import static com.evolveum.midpoint.prism.query.PrismQuerySerialization.NotSupportedException; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; @@ -33,6 +36,7 @@ import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.PrismQueryLanguageParser; +import com.evolveum.midpoint.prism.query.PrismQuerySerialization; import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.util.DOMUtil; @@ -69,11 +73,28 @@ private void verify(String query, ObjectFilter original, PrismObject user) th try { assertEquals(dslFilter.toString(), original.toString()); assertEquals(dslResult, javaResult, "Filters do not match."); + + //String javaSerialized = serialize(original); + String dslSerialized = serialize(dslFilter); + + //assertEquals(javaSerialized, query); + assertEquals(dslSerialized, query); + } catch (AssertionError e) { throw new AssertionError(e.getMessage() + "for filter: \n " + query); } } + private String serialize(ObjectFilter original) { + PrismQuerySerialization serialization; + try { + serialization = new PrismQuerySerializerImpl().serialize(original); + return serialization.filterText(); + } catch (NotSupportedException e) { + throw new AssertionError(e); + } + } + @Test public void testMatchAndFilter() throws SchemaException, IOException { ObjectFilter filter = @@ -86,10 +107,10 @@ public void testMatchAndFilter() throws SchemaException, IOException { @Test public void testPathComparison() throws SchemaException, IOException { - ObjectFilter dslFilter = parse("fullName != givenName"); + ObjectFilter dslFilter = parse("fullName not equal givenName"); boolean match = ObjectQuery.match(parseUserJacky(),dslFilter,MATCHING_RULE_REGISTRY); assertTrue(match); - verify("fullName not equal givenName", dslFilter); + verify("fullName != givenName", dslFilter); } @Test @@ -106,7 +127,7 @@ public void testExistsPositive() throws Exception { .exists(UserType.F_ASSIGNMENT) .item(AssignmentType.F_DESCRIPTION).eq("Assignment 2") .buildFilter(); - verify("assignment matches ( description = 'Assignment 2'", filter); + verify("assignment matches (description = 'Assignment 2')", filter); } @Test @@ -143,7 +164,7 @@ public void testMatchEqualMultivalue() throws Exception { ObjectFilter filter = getPrismContext().queryFor(UserType.class) .item(ItemPath.create(UserType.F_EXTENSION, "indexedString"), def).eq("alpha") .buildFilter(); - verify("extension/indexedString = 'alpha' ", filter); + verify("extension/indexedString = 'alpha'", filter); } @Test @@ -185,14 +206,14 @@ public void testComplexMatch() throws Exception { .endBlock() .buildFilter(); verify("familyName = 'Sparrow' and fullName contains 'arr' " - + "and ( givenName = 'Jack' or givenName = 'Jackie' ) ", filter); + + "and (givenName = 'Jack' or givenName = 'Jackie')", filter); } @Test public void testPolystringMatchEqualFilter() throws Exception { PolyString name = new PolyString("jack", "jack"); ObjectFilter filter = getPrismContext().queryFor(UserType.class) - .item(UserType.F_NAME).eq(name) + .item(UserType.F_NAME).eq(name).matchingStrict() .buildFilter(); verify("name matches (orig = 'jack' and norm = 'jack')", filter); verify("name matches (norm = 'jack')", @@ -242,7 +263,7 @@ public void testMultiRootPositive() throws Exception { ObjectFilter filter = getPrismContext().queryFor(UserType.class) .item(UserType.F_ASSIGNMENT, AssignmentType.F_DESCRIPTION).eq("Assignment 2") .buildFilter(); - verify(" assignment/description = 'Assignment 2'", filter); + verify("assignment/description = 'Assignment 2'", filter); } @Test // MID-4217 @@ -259,7 +280,7 @@ public void testRefPositive() throws Exception { ObjectFilter filter = getPrismContext().queryFor(UserType.class) .item(UserType.F_ACCOUNT_REF).ref("c0c010c0-d34d-b33f-f00d-aaaaaaaa1113") .buildFilter(); - verify("accountRef matches ( oid = 'c0c010c0-d34d-b33f-f00d-aaaaaaaa1113')",filter); + verify("accountRef matches (oid = 'c0c010c0-d34d-b33f-f00d-aaaaaaaa1113')",filter); } @Test @@ -268,7 +289,7 @@ public void testOidIn() throws Exception { .id("c0c010c0-d34d-b33f-f00d-aaaaaaaa1113", "c0c010c0-d34d-b33f-f00d-aaaaaaaa1114", "c0c010c0-d34d-b33f-f00d-aaaaaaaa1115") .buildFilter(); - verify(". inOid ('c0c010c0-d34d-b33f-f00d-aaaaaaaa1113', \"c0c010c0-d34d-b33f-f00d-aaaaaaaa1114\", \"c0c010c0-d34d-b33f-f00d-aaaaaaaa1115\" )",filter); + verify(". inOid ('c0c010c0-d34d-b33f-f00d-aaaaaaaa1113', 'c0c010c0-d34d-b33f-f00d-aaaaaaaa1114', 'c0c010c0-d34d-b33f-f00d-aaaaaaaa1115')",filter); } @@ -278,7 +299,7 @@ public void testRefNegative() throws Exception { ObjectFilter filter = getPrismContext().queryFor(UserType.class) .item(UserType.F_ACCOUNT_REF).ref("xxxxxxxxxxxxxx") .buildFilter(); - verify("accountRef matches ( oid = 'xxxxxxxxxxxxxx')",filter); + verify("accountRef matches (oid = 'xxxxxxxxxxxxxx')",filter); } @Test @@ -390,7 +411,7 @@ private void assertDateTimeLtFilter(PrismObject user, Object value, bo private String toText(Object value) { if (value instanceof XMLGregorianCalendar) { - return new StringBuilder().append('"').append(value.toString()).append('"').toString(); + return new StringBuilder().append("'").append(value.toString()).append("'").toString(); } return value.toString(); }