From 342ef68448beb2dbf1e7705671074ce461050948 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 1 Mar 2020 13:14:00 +0100 Subject: [PATCH] Add Binding/DOM Query language adapter Provide implementation of binding language translation on top of its DOM counterpart. On the DOM layer the expression can be transmitted, hence it gives the possibility to move the execution to storage backend, thus reducing app/backend data interchange volume. Change-Id: I51c8ec7e34c2485f62aeb5bdbe35fe1507cabaa9 Signed-off-by: Robert Varga --- .../binding/api/query/QueryExecutor.java | 18 ++ .../adapter/AbstractValueMatchBuilder.java | 55 +++++ .../DefaultComparableMatchBuilder.java | 46 ++++ .../DefaultDescendantQueryBuilder.java | 58 +++++ .../adapter/DefaultMatchBuilderPath.java | 125 +++++++++++ .../query/binding/adapter/DefaultQuery.java | 35 +++ .../binding/adapter/DefaultQueryFactory.java | 42 ++++ .../adapter/DefaultStringMatchBuilder.java | 47 ++++ .../binding/adapter/DefaultValueMatch.java | 36 +++ .../adapter/DefaultValueMatchBuilder.java | 19 ++ .../query/binding/adapter/LambdaDecoder.java | 93 ++++++++ .../binding/adapter/OSGiQueryFactory.java | 49 ++++ .../binding/adapter/QueryBuilderState.java | 108 +++++++++ .../binding/adapter/QueryBuilderTest.java | 50 +++++ .../mdsal/dom/api/query/DOMQuery.java | 44 ++++ .../mdsal/dom/api/query/DOMQueryLike.java | 26 +++ .../dom/api/query/DOMQueryPredicate.java | 210 ++++++++++++++++++ .../dom/spi/query/DOMQueryEvaluator.java | 73 ++++++ 18 files changed, 1134 insertions(+) create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java create mode 100644 binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java create mode 100644 dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java create mode 100644 dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java create mode 100644 dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java create mode 100644 dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java new file mode 100644 index 0000000000..490aed31f5 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.api.query; + +import com.google.common.annotations.Beta; +import com.google.common.util.concurrent.ListenableFuture; +import org.opendaylight.yangtools.yang.binding.DataObject; + +@Beta +public interface QueryExecutor { + + ListenableFuture> executeQuery(QueryExpression query); +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java new file mode 100644 index 0000000000..a6eae4f412 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/AbstractValueMatchBuilder.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static java.util.Objects.requireNonNull; + +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.api.query.ValueMatch; +import org.opendaylight.mdsal.binding.api.query.ValueMatchBuilder; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Exists; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.ValueEquals; +import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +abstract class AbstractValueMatchBuilder implements ValueMatchBuilder { + private final QueryBuilderState builder; + private final InstanceIdentifier select; + private final BoundMethod method; + + AbstractValueMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier select, + final BoundMethod method) { + this.builder = requireNonNull(builder); + this.select = requireNonNull(select); + this.method = requireNonNull(method); + } + + @Override + public final ValueMatch nonNull() { + return withPredicate(new Exists(relativePath())); + } + + @Override + public final ValueMatch valueEquals(final V value) { + return withPredicate(new ValueEquals<>(relativePath(), value)); + } + + final YangInstanceIdentifier relativePath() { + return method.parentPath.node(((DataSchemaNode) method.methodCodec.getSchema()).getQName()); + } + + final @NonNull ValueMatch withPredicate(final DOMQueryPredicate predicate) { + // FIXME: this does not quite take value codec into account :( + builder.addPredicate(predicate); + return new DefaultValueMatch<>(builder, select); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java new file mode 100644 index 0000000000..7565edfd28 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultComparableMatchBuilder.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import org.opendaylight.mdsal.binding.api.query.ComparableMatchBuilder; +import org.opendaylight.mdsal.binding.api.query.ValueMatch; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.GreaterThan; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.GreaterThanOrEqual; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.LessThan; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.LessThanOrEqual; +import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +final class DefaultComparableMatchBuilder> + extends AbstractValueMatchBuilder implements ComparableMatchBuilder { + DefaultComparableMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier select, + final BoundMethod method) { + super(builder, select, method); + } + + @Override + public ValueMatch lessThan(final V value) { + return withPredicate(new LessThan<>(relativePath(), value)); + } + + @Override + public ValueMatch lessThanOrEqual(final V value) { + return withPredicate(new LessThanOrEqual<>(relativePath(), value)); + } + + @Override + public ValueMatch greaterThan(final V value) { + return withPredicate(new GreaterThan<>(relativePath(), value)); + } + + @Override + public ValueMatch greaterThanOrEqual(final V value) { + return withPredicate(new GreaterThanOrEqual<>(relativePath(), value)); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java new file mode 100644 index 0000000000..a26ff8ddab --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultDescendantQueryBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder; +import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath; +import org.opendaylight.mdsal.binding.api.query.QueryExpression; +import org.opendaylight.mdsal.binding.api.query.QueryStructureException; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree; +import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.ChoiceIn; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder; + +final class DefaultDescendantQueryBuilder + implements DescendantQueryBuilder { + private final InstanceIdentifierBuilder childPath; + private final QueryBuilderState builder; + + DefaultDescendantQueryBuilder(final BindingCodecTree codec, final InstanceIdentifier rootPath) { + this.builder = new QueryBuilderState(codec, rootPath); + this.childPath = rootPath.builder(); + } + + @Override + @SuppressWarnings("unchecked") + public > DescendantQueryBuilder extractChild(final Class childClass) { + childPath.child(childClass); + return (DefaultDescendantQueryBuilder) this; + } + + @Override + @SuppressWarnings("unchecked") + public & DataObject, N extends ChildOf> + DescendantQueryBuilder extractChild(final Class caseClass, final Class childClass) { + childPath.child(caseClass, childClass); + return (DefaultDescendantQueryBuilder) this; + } + + @Override + public MatchBuilderPath matching() { + final InstanceIdentifier selectPath = childPath.build(); + builder.setSelectPath(selectPath); + return new DefaultMatchBuilderPath<>(builder, selectPath, childPath); + } + + @Override + public QueryExpression build() throws QueryStructureException { + builder.setSelectPath(childPath.build()); + return builder.buildQuery(); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java new file mode 100644 index 0000000000..28de9e1293 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultMatchBuilderPath.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static java.util.Objects.requireNonNull; + +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.api.query.ComparableMatchBuilder; +import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath; +import org.opendaylight.mdsal.binding.api.query.StringMatchBuilder; +import org.opendaylight.mdsal.binding.api.query.ValueMatchBuilder; +import org.opendaylight.yangtools.yang.binding.BaseIdentity; +import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.ChoiceIn; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder; +import org.opendaylight.yangtools.yang.binding.TypeObject; +import org.opendaylight.yangtools.yang.common.Empty; +import org.opendaylight.yangtools.yang.common.Uint16; +import org.opendaylight.yangtools.yang.common.Uint32; +import org.opendaylight.yangtools.yang.common.Uint64; +import org.opendaylight.yangtools.yang.common.Uint8; + +final class DefaultMatchBuilderPath implements MatchBuilderPath { + private final QueryBuilderState builder; + private final InstanceIdentifier select; + private final InstanceIdentifierBuilder target; + + DefaultMatchBuilderPath(final QueryBuilderState builder, final InstanceIdentifier select, + final InstanceIdentifierBuilder target) { + this.builder = requireNonNull(builder); + this.select = requireNonNull(select); + this.target = requireNonNull(target); + } + + @Override + @SuppressWarnings("unchecked") + public > MatchBuilderPath childObject(final Class childClass) { + target.child(childClass); + return (MatchBuilderPath) this; + } + + @Override + @SuppressWarnings("unchecked") + public & DataObject, N extends ChildOf> + MatchBuilderPath extractChild(final Class caseClass, final Class childClass) { + target.child(caseClass, childClass); + return (MatchBuilderPath) this; + } + + @Override + public ValueMatchBuilder leaf(final EmptyLeafReference methodRef) { + return defaultFor(methodRef); + } + + @Override + public StringMatchBuilder leaf(final StringLeafReference methodRef) { + return new DefaultStringMatchBuilder<>(builder, select, builder.bindMethod(target.build(), methodRef)); + } + + @Override + public ComparableMatchBuilder leaf(final Int8LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Int16LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Int32LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Int64LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Uint8LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Uint16LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Uint32LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ComparableMatchBuilder leaf(final Uint64LeafReference methodRef) { + return comparableFor(methodRef); + } + + @Override + public ValueMatchBuilder leaf(final IdentityLeafReference methodRef) { + return defaultFor(methodRef); + } + + @Override + public ValueMatchBuilder leaf(final TypeObjectLeafReference methodRef) { + return defaultFor(methodRef); + } + + private @NonNull ValueMatchBuilder defaultFor(final LeafReference ref) { + return new DefaultValueMatchBuilder<>(builder, select, builder.bindMethod(target.build(), ref)); + } + + private > @NonNull ComparableMatchBuilder comparableFor( + final LeafReference ref) { + return new DefaultComparableMatchBuilder<>(builder, select, builder.bindMethod(target.build(), ref)); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java new file mode 100644 index 0000000000..80361532c4 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQuery.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.api.query.QueryExpression; +import org.opendaylight.mdsal.dom.api.query.DOMQuery; +import org.opendaylight.mdsal.dom.api.query.DOMQueryLike; +import org.opendaylight.yangtools.yang.binding.DataObject; + +final class DefaultQuery implements QueryExpression, DOMQueryLike { + private final @NonNull DOMQuery domQuery; + + DefaultQuery(final DOMQuery domQuery) { + this.domQuery = requireNonNull(domQuery); + } + + @Override + public DOMQuery asDOMQuery() { + return domQuery; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("dom", domQuery).toString(); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java new file mode 100644 index 0000000000..e3cee0dded --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultQueryFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import java.util.ServiceLoader; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.kohsuke.MetaInfServices; +import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder; +import org.opendaylight.mdsal.binding.api.query.QueryFactory; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +@Beta +@MetaInfServices +@Singleton +public final class DefaultQueryFactory implements QueryFactory { + private final BindingCodecTree codec; + + public DefaultQueryFactory() { + this(ServiceLoader.load(BindingCodecTree.class).findFirst().orElseThrow()); + } + + @Inject + public DefaultQueryFactory(final BindingCodecTree codec) { + this.codec = requireNonNull(codec); + } + + @Override + public DescendantQueryBuilder querySubtree(final InstanceIdentifier rootPath) { + return new DefaultDescendantQueryBuilder<>(codec, rootPath); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java new file mode 100644 index 0000000000..00e042d21e --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultStringMatchBuilder.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import java.util.regex.Pattern; +import org.opendaylight.mdsal.binding.api.query.StringMatchBuilder; +import org.opendaylight.mdsal.binding.api.query.ValueMatch; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Contains; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.EndsWith; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.MatchesPattern; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.StartsWith; +import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +final class DefaultStringMatchBuilder extends AbstractValueMatchBuilder + implements StringMatchBuilder { + DefaultStringMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier select, + final BoundMethod method) { + super(builder, select, method); + } + + @Override + public ValueMatch startsWith(final String str) { + return withPredicate(new StartsWith(relativePath(), str)); + } + + @Override + public ValueMatch endsWith(final String str) { + return withPredicate(new EndsWith(relativePath(), str)); + } + + @Override + public ValueMatch contains(final String str) { + return withPredicate(new Contains(relativePath(), str)); + } + + @Override + public ValueMatch matchesPattern(final Pattern pattern) { + return withPredicate(new MatchesPattern(relativePath(), pattern)); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java new file mode 100644 index 0000000000..1152fb6c03 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatch.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static java.util.Objects.requireNonNull; + +import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath; +import org.opendaylight.mdsal.binding.api.query.QueryExpression; +import org.opendaylight.mdsal.binding.api.query.ValueMatch; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +final class DefaultValueMatch implements ValueMatch { + private final QueryBuilderState builder; + private final InstanceIdentifier select; + + DefaultValueMatch(final QueryBuilderState builder, final InstanceIdentifier select) { + this.builder = requireNonNull(builder); + this.select = requireNonNull(select); + } + + @Override + public MatchBuilderPath and() { + return new DefaultMatchBuilderPath<>(builder, select, select.builder()); + } + + @Override + public QueryExpression build() { + return builder.buildQuery(); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java new file mode 100644 index 0000000000..bf4902f113 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/DefaultValueMatchBuilder.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import org.opendaylight.mdsal.query.binding.adapter.QueryBuilderState.BoundMethod; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +final class DefaultValueMatchBuilder extends AbstractValueMatchBuilder { + DefaultValueMatchBuilder(final QueryBuilderState builder, final InstanceIdentifier select, + final BoundMethod method) { + super(builder, select, method); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java new file mode 100644 index 0000000000..18fc7b5447 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/LambdaDecoder.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath.LeafReference; +import org.opendaylight.yangtools.concepts.Immutable; + +/** + * Utility class for forcing decoding lambda instances to the method being invoked. The theory here is that + * {@code MatchBuilderPath.leaf()} methods are expected to be used with {@code method references}, which are converted + * to {@link LeafReference} lambdas. + * + *

+ * We then assume runtime is following guidance around {@link SerializedLambda}, thus Serializable lambdas have a + * {@code writeReplace()} method and that it produces {@link SerializedLambda} -- which we use to get the information + * about what the lambda does at least in the single case we support. + * + *

+ * An alternative approach to cracking the lambda would be to generate a dynamic proxy implementation of the base + * DataObject (we have the Class to do that), back it by a invocation handler which throws a private RuntimeException + * subclass containing the name of the invoked method. We then would invoke the lambda on such a proxy and intercept + * the exception raised. This unfortunately has multiple downsides: + *

    + *
  • it requires a properly-managed ClassLoader (or pollutes original classloader with the proxy class)
  • + *
  • it makes it appear we support something else than method references, which we do not
  • + *
  • it creates additional implementation of the interface, bringing the system-wide total to 3, which can hurt + * JIT's decisions
  • + *
+ */ +final class LambdaDecoder { + static final class LambdaTarget implements Immutable { + final String targetClass; + final String targetMethod; + + LambdaTarget(final String targetClass, final String targetMethod) { + this.targetClass = requireNonNull(targetClass); + this.targetMethod = requireNonNull(targetMethod); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("class", targetClass).add("method", targetMethod).toString(); + } + } + + private static final LoadingCache, Method> REPLACE_CACHE = CacheBuilder.newBuilder() + .weakKeys().weakValues().build(new CacheLoader, Method>() { + @Override + public Method load(final Class key) throws PrivilegedActionException { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + final Method method = key.getDeclaredMethod("writeReplace"); + method.setAccessible(true); + return method; + }); + } + }); + private static final LoadingCache, LambdaTarget> LAMBDA_CACHE = CacheBuilder.newBuilder() + .weakKeys().build(new CacheLoader, LambdaTarget>() { + @Override + public LambdaTarget load(final LeafReference ref) throws Exception { + final Object replaced = REPLACE_CACHE.get(ref.getClass()).invoke(ref); + verify(replaced instanceof SerializedLambda, "Unexpected replaced object %s", replaced); + final SerializedLambda serialized = (SerializedLambda) replaced; + return new LambdaTarget(serialized.getImplClass().replace('/', '.'), + serialized.getImplMethodName()); + } + }); + + private LambdaDecoder() { + // Hidden on purpose + } + + static LambdaTarget resolveLambda(final LeafReference lambda) { + return LAMBDA_CACHE.getUnchecked(lambda); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java new file mode 100644 index 0000000000..6e57fd0601 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/OSGiQueryFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import com.google.common.annotations.Beta; +import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder; +import org.opendaylight.mdsal.binding.api.query.QueryFactory; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +@Component(immediate = true) +public final class OSGiQueryFactory implements QueryFactory { + private static final Logger LOG = LoggerFactory.getLogger(OSGiQueryFactory.class); + + @Reference + BindingCodecTree codec; + + private DefaultQueryFactory delegate; + + @Override + public DescendantQueryBuilder querySubtree(final InstanceIdentifier rootPath) { + return delegate.querySubtree(rootPath); + } + + @Activate + void activate() { + delegate = new DefaultQueryFactory(codec); + LOG.info("Binding Query activated"); + } + + @Deactivate + void deactivate() { + delegate = null; + LOG.info("Binding Query deactivated"); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java new file mode 100644 index 0000000000..7686c61c74 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderState.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath.LeafReference; +import org.opendaylight.mdsal.binding.api.query.QueryExpression; +import org.opendaylight.mdsal.binding.api.query.QueryStructureException; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode; +import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping; +import org.opendaylight.mdsal.dom.api.query.DOMQuery; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate; +import org.opendaylight.mdsal.query.binding.adapter.LambdaDecoder.LambdaTarget; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus; + +final class QueryBuilderState { + static final class BoundMethod implements Immutable { + final @NonNull YangInstanceIdentifier parentPath; + final @NonNull BindingCodecTreeNode methodCodec; + + BoundMethod(final YangInstanceIdentifier parentPath, final BindingCodecTreeNode methodCodec) { + this.parentPath = requireNonNull(parentPath); + this.methodCodec = requireNonNull(methodCodec); + } + } + + private final BindingCodecTree codec; + + private final List predicates = new ArrayList<>(); + private final YangInstanceIdentifier root; + private YangInstanceIdentifier absoluteSelect; + private YangInstanceIdentifier relativeSelect; + + QueryBuilderState(final BindingCodecTree codec, final InstanceIdentifier root) { + this.codec = requireNonNull(codec); + this.root = fromBinding(root); + } + + void setSelectPath(final @NonNull InstanceIdentifier selectPath) { + checkState(root != null, "Root path has not been set yet"); + checkState(relativeSelect == null, "Select path has already been set to %s", relativeSelect); + + absoluteSelect = fromBinding(selectPath); + relativeSelect = absoluteSelect.relativeTo(root) + .orElseThrow(() -> new IllegalStateException(root + " is not an ancestor of " + absoluteSelect)); + } + + @NonNull BoundMethod bindMethod(final @NonNull InstanceIdentifier bindingPath, + final @NonNull LeafReference ref) { + // Verify bindingPath, which will give us something to fish in + final BindingDataObjectCodecTreeNode targetCodec = codec.getSubtreeCodec(bindingPath); + checkState(targetCodec != null, "Failed to find codec for %s", bindingPath); + + final WithStatus targetSchema = targetCodec.getSchema(); + verify(targetSchema instanceof DataNodeContainer, "Unexpected target schema %s", targetSchema); + + final LambdaTarget targetLeaf = LambdaDecoder.resolveLambda(ref); + verify(targetLeaf.targetClass.equals(bindingPath.getTargetType().getName()), "Mismatched target %s and path %s", + targetLeaf, bindingPath); + final DataSchemaNode child = findChild((DataNodeContainer) targetSchema, targetLeaf.targetMethod); + final YangInstanceIdentifier absTarget = fromBinding(bindingPath); + final YangInstanceIdentifier relTarget = absTarget.relativeTo(absoluteSelect) + .orElseThrow(() -> new IllegalStateException(absoluteSelect + " is not an ancestor of " + absTarget)); + + return new BoundMethod(relTarget, targetCodec.yangPathArgumentChild(new NodeIdentifier(child.getQName()))); + } + + void addPredicate(final DOMQueryPredicate predicate) { + predicates.add(requireNonNull(predicate)); + } + + @NonNull QueryExpression buildQuery() { + return new DefaultQuery<>(new DOMQuery(root, relativeSelect, predicates)); + } + + private @NonNull YangInstanceIdentifier fromBinding(final InstanceIdentifier bindingId) { + return codec.getInstanceIdentifierCodec().fromBinding(bindingId); + } + + private static DataSchemaNode findChild(final DataNodeContainer parent, final String methodName) { + for (DataSchemaNode child : parent.getChildNodes()) { + if (methodName.equals(BindingSchemaMapping.getGetterMethodName(child))) { + return child; + } + } + throw new QueryStructureException("Failed to find schema matching " + methodName + " in " + parent); + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java new file mode 100644 index 0000000000..23d9b0c3d9 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/query/binding/adapter/QueryBuilderTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.query.binding.adapter; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.binding.runtime.spi.BindingRuntimeHelpers; +import org.opendaylight.mdsal.binding.api.query.QueryExpression; +import org.opendaylight.mdsal.binding.api.query.QueryFactory; +import org.opendaylight.mdsal.binding.api.query.QueryStructureException; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree; +import org.opendaylight.mdsal.binding.dom.codec.impl.DefaultBindingCodecTreeFactory; +import org.opendaylight.mdsal.binding.generator.impl.DefaultBindingRuntimeGenerator; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.top.level.list.NestedList; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class QueryBuilderTest { + private static BindingCodecTree CODEC; + + private final QueryFactory factory = new DefaultQueryFactory(CODEC); + + @BeforeClass + public static void beforeClass() { + CODEC = new DefaultBindingCodecTreeFactory().create(BindingRuntimeHelpers.createRuntimeContext( + new DefaultBindingRuntimeGenerator())); + } + + @Test + public void bar() throws QueryStructureException { + final QueryExpression query = factory.querySubtree(InstanceIdentifier.builder(Top.class).build()) + .extractChild(TopLevelList.class) + .matching() + .childObject(NestedList.class) + .leaf(NestedList::getName).contains("foo") + .and().leaf(TopLevelList::getName).valueEquals("bar") + .build(); +// +// // Execution start +// final Result result = query.getResult(); +// // Execution fetch +// final TopLevelList value = result.getValue(); + } +} diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java new file mode 100644 index 0000000000..81cc44e623 --- /dev/null +++ b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQuery.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.api.query; + +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +@Beta +public final class DOMQuery implements Immutable { + private final @NonNull YangInstanceIdentifier root; + // Note: relative to root + private final @NonNull YangInstanceIdentifier select; + private final @NonNull ImmutableList predicates; + + public DOMQuery(final YangInstanceIdentifier root, final YangInstanceIdentifier select, + final List predicates) { + this.root = requireNonNull(root); + this.select = requireNonNull(select); + this.predicates = ImmutableList.copyOf(predicates); + } + + public @NonNull YangInstanceIdentifier getRoot() { + return root; + } + + public @NonNull YangInstanceIdentifier getSelect() { + return select; + } + + public @NonNull List getPredicates() { + return predicates; + } +} diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java new file mode 100644 index 0000000000..c0913ac760 --- /dev/null +++ b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryLike.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.api.query; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.concepts.Immutable; + +/** + * Trait for objects which can be formulated in terms of a {@link DOMQuery}. + */ +@Beta +@NonNullByDefault +public interface DOMQueryLike extends Immutable { + /** + * Return a {@link DOMQuery} view of this object. + * + * @return A DOMQuery + */ + DOMQuery asDOMQuery(); +} diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java new file mode 100644 index 0000000000..735126c18c --- /dev/null +++ b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/query/DOMQueryPredicate.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.api.query; + +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +@Beta +public abstract class DOMQueryPredicate implements Immutable, Predicate { + abstract static class AbstractValueDOMQueryPredicate extends DOMQueryPredicate { + private final @NonNull T value; + + AbstractValueDOMQueryPredicate(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath); + this.value = requireNonNull(value); + } + + final @NonNull T value() { + return value; + } + + @Override + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper.add("value", value); + } + } + + abstract static class AbstractComparableDOMQueryPredicate> + extends AbstractValueDOMQueryPredicate { + AbstractComparableDOMQueryPredicate(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath, value); + } + + @Override + @SuppressWarnings("unchecked") + public final boolean test(final Object data) { + return data != null && test(value().compareTo((T) data)); + } + + abstract boolean test(int valueToData); + } + + abstract static class AbstractStringDOMQueryPredicate extends AbstractValueDOMQueryPredicate { + AbstractStringDOMQueryPredicate(final YangInstanceIdentifier relativePath, final String value) { + super(relativePath, value); + } + + @Override + public final boolean test(final Object data) { + return data instanceof String && test((String) data); + } + + abstract boolean test(@NonNull String str); + } + + public static final class Exists extends DOMQueryPredicate { + public Exists(final YangInstanceIdentifier relativePath) { + super(relativePath); + } + + @Override + public boolean test(final Object data) { + return data != null; + } + } + + public static final class ValueEquals extends AbstractValueDOMQueryPredicate { + public ValueEquals(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath, value); + } + + @Override + public boolean test(final Object data) { + return value().equals(data); + } + } + + public static final class GreaterThan> extends AbstractComparableDOMQueryPredicate { + public GreaterThan(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath, value); + } + + @Override + boolean test(final int valueToData) { + return valueToData <= 0; + } + } + + public static final class GreaterThanOrEqual> + extends AbstractComparableDOMQueryPredicate { + public GreaterThanOrEqual(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath, value); + } + + @Override + boolean test(final int valueToData) { + return valueToData < 0; + } + } + + public static final class LessThan> extends AbstractComparableDOMQueryPredicate { + public LessThan(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath, value); + } + + @Override + boolean test(final int valueToData) { + return valueToData >= 0; + } + } + + public static final class LessThanOrEqual> extends AbstractComparableDOMQueryPredicate { + public LessThanOrEqual(final YangInstanceIdentifier relativePath, final T value) { + super(relativePath, value); + } + + @Override + boolean test(final int valueToData) { + return valueToData > 0; + } + } + + public static final class StartsWith extends AbstractStringDOMQueryPredicate { + public StartsWith(final YangInstanceIdentifier relativePath, final String str) { + super(relativePath, str); + } + + @Override + boolean test(final String str) { + return str.startsWith(value()); + } + } + + public static final class EndsWith extends AbstractStringDOMQueryPredicate { + public EndsWith(final YangInstanceIdentifier relativePath, final String str) { + super(relativePath, str); + } + + @Override + boolean test(final String str) { + return str.endsWith(value()); + } + } + + public static final class Contains extends AbstractStringDOMQueryPredicate { + public Contains(final YangInstanceIdentifier relativePath, final String str) { + super(relativePath, str); + } + + @Override + boolean test(final String str) { + return str.contains(value()); + } + } + + public static final class MatchesPattern extends DOMQueryPredicate { + private final Pattern pattern; + + public MatchesPattern(final YangInstanceIdentifier relativePath, final Pattern pattern) { + super(relativePath); + this.pattern = requireNonNull(pattern); + } + + @Override + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper.add("pattern", pattern); + } + + @Override + public boolean test(final Object data) { + return data instanceof CharSequence && pattern.matcher((CharSequence) data).matches(); + } + } + + private final @NonNull YangInstanceIdentifier relativePath; + + DOMQueryPredicate(final YangInstanceIdentifier relativePath) { + this.relativePath = requireNonNull(relativePath); + } + + public final @NonNull YangInstanceIdentifier getPath() { + return relativePath; + } + + @Override + public abstract boolean test(@Nullable Object data); + + @Override + public String toString() { + return addToStringAttributes(MoreObjects.toStringHelper(this).add("path", relativePath)).toString(); + } + + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper; + } +} diff --git a/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java b/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java new file mode 100644 index 0000000000..d7a9dde6e3 --- /dev/null +++ b/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/query/DOMQueryEvaluator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.spi.query; + +import com.google.common.collect.ImmutableList; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import org.opendaylight.mdsal.dom.api.query.DOMQuery; +import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; + +public final class DOMQueryEvaluator { + private DOMQueryEvaluator() { + + } + + public static List>> evaluate(final DOMQuery query, + final NormalizedNode root) { + final YangInstanceIdentifier path = query.getSelect(); + return path.isEmpty() ? evalSingle(root, query) + : evalPath(new ArrayDeque<>(path.getPathArguments()), root, query); + } + + private static List>> evalPath( + final ArrayDeque remaining, final NormalizedNode data, final DOMQuery query) { + final List>> result = new ArrayList<>(); + evalPath(result, query.getRoot(), remaining, data, query); + return result; + } + + private static void evalPath(final List>> result, + final YangInstanceIdentifier path, final ArrayDeque remaining, + final NormalizedNode data, final DOMQuery query) { + final PathArgument next = remaining.poll(); + if (next == null) { + if (matches(data, query)) { + result.add(new SimpleImmutableEntry<>(query.getRoot(), data)); + } + return; + } + + // TODO: this is probably insufficient + NormalizedNodes.findNode(data, next) + .ifPresent(child -> evalPath(result, path.node(next), remaining, child, query)); + remaining.push(next); + } + + private static List>> evalSingle( + final NormalizedNode data, final DOMQuery query) { + return matches(data, query) ? ImmutableList.of() + : ImmutableList.of(new SimpleImmutableEntry<>(query.getRoot(), data)); + } + + private static boolean matches(final NormalizedNode data, final DOMQuery query) { + for (DOMQueryPredicate pred : query.getPredicates()) { + if (!pred.test(data)) { + return false; + } + } + return true; + } +} -- 2.36.6