From ffe8f4ea13bd2bab97514aa1d6ee257a1d7b39ff Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Thu, 19 Nov 2020 22:30:20 +0100 Subject: [PATCH] Split out AbstractValidation MinMaxElementsValidation performs a useful role as a template for how we can create simple subtree validations. Split the common bits into AbstractValidation and elinate code duplication by doing some trickery. JIRA: YANGTOOLS-1177 Change-Id: I5adbed16e85f9752a0ce5061b0159c8124583346 Signed-off-by: Robert Varga --- .../impl/schema/tree/AbstractValidation.java | 139 ++++++++++++++++++ .../schema/tree/MinMaxElementsValidation.java | 122 +++------------ .../tree/ListConstraintsValidation.java | 4 +- .../yang/data/impl/schema/tree/YT776Test.java | 4 +- 4 files changed, 166 insertions(+), 103 deletions(-) create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractValidation.java diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractValidation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractValidation.java new file mode 100644 index 0000000000..98ff474db5 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractValidation.java @@ -0,0 +1,139 @@ +/* + * 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.yangtools.yang.data.impl.schema.tree; + +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects.ToStringHelper; +import java.util.Optional; +import org.eclipse.jdt.annotation.NonNull; +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.tree.DataValidationFailedException; +import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A forwarding {@link ModificationApplyOperation}. Useful for strategies which do not deal with data layout, but rather + * perform additional validation. + */ +abstract class AbstractValidation extends ModificationApplyOperation { + private static final Logger LOG = LoggerFactory.getLogger(AbstractValidation.class); + + private final @NonNull ModificationApplyOperation delegate; + + AbstractValidation(final ModificationApplyOperation delegate) { + this.delegate = requireNonNull(delegate); + } + + @Override + public final Optional getChild(final PathArgument child) { + return delegate.getChild(child); + } + + @Override + final ChildTrackingPolicy getChildPolicy() { + return delegate.getChildPolicy(); + } + + @Override + final void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode value, final Version version) { + delegate.mergeIntoModifiedNode(node, value, version); + } + + @Override + final void quickVerifyStructure(final NormalizedNode modification) { + delegate.quickVerifyStructure(modification); + } + + @Override + final void recursivelyVerifyStructure(final NormalizedNode value) { + delegate.recursivelyVerifyStructure(value); + } + + @Override + final Optional apply(final ModifiedNode modification, + final Optional storeMeta, final Version version) { + Optional ret = modification.getValidatedNode(this, storeMeta); + if (ret == null) { + // This might also mean the delegate is maintaining validation + if (delegate instanceof AbstractValidation) { + ret = modification.getValidatedNode(delegate, storeMeta); + if (ret != null) { + return ret; + } + } + + // Deal with the result moving on us + ret = delegate.apply(modification, storeMeta, version); + ret.ifPresent(meta -> enforceOnData(meta.getData())); + } + return ret; + } + + @Override + final void checkApplicable(final ModificationPath path, final NodeModification modification, + final Optional current, final Version version) throws DataValidationFailedException { + delegate.checkApplicable(path, modification, current, version); + if (!(modification instanceof ModifiedNode)) { + // FIXME: 7.0.0: turn this into a verify? + LOG.debug("Could not validate {}, does not implement expected class {}", modification, ModifiedNode.class); + return; + } + + final ModifiedNode modified = (ModifiedNode) modification; + if (delegate instanceof AbstractValidation) { + checkApplicable(path, verifyNotNull(modified.getValidatedNode(delegate, current))); + return; + } + + // We need to actually perform the operation to deal with merge in a sane manner. We know the modification + // is immutable, so the result of validation will probably not change. Note we should not be checking number + final Optional applied = delegate.apply(modified, current, version); + checkApplicable(path, applied); + + // Everything passed. We now have a snapshot of the result node, it would be too bad if we just threw it out. + // We know what the result of an apply operation is going to be *if* the following are kept unchanged: + // - the 'current' node + // - the effective model context (therefore, the fact this object is associated with the modification) + // + // So let's stash the result. We will pick it up during apply operation. + modified.setValidatedNode(this, current, applied); + } + + private void checkApplicable(final ModificationPath path, final Optional applied) + throws DataValidationFailedException { + if (applied.isPresent()) { + // We only enforce min/max on present data and rely on MandatoryLeafEnforcer to take care of the empty case + enforceOnData(path, applied.orElseThrow().getData()); + } + } + + @Override + void fullVerifyStructure(final NormalizedNode modification) { + delegate.fullVerifyStructure(modification); + enforceOnData(modification); + } + + final @NonNull ModificationApplyOperation delegate() { + return delegate; + } + + abstract void enforceOnData(ModificationPath path, NormalizedNode value) + throws DataValidationFailedException; + + abstract void enforceOnData(@NonNull NormalizedNode data); + + @Override + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper.add("delegate", delegate); + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java index 8e1e91c563..f5c75a6524 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java @@ -7,36 +7,26 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema.tree; -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.requireNonNull; - import com.google.common.base.MoreObjects.ToStringHelper; import java.util.Optional; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; -import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; import org.opendaylight.yangtools.yang.data.api.schema.tree.RequiredElementCountException; -import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode; -import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint; import org.opendaylight.yangtools.yang.model.api.ElementCountConstraintAware; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; final class MinMaxElementsValidation - extends ModificationApplyOperation { - private static final Logger LOG = LoggerFactory.getLogger(MinMaxElementsValidation.class); - - private final SchemaAwareApplyOperation delegate; + extends AbstractValidation { private final int minElements; private final int maxElements; private MinMaxElementsValidation(final SchemaAwareApplyOperation delegate, final Integer minElements, final Integer maxElements) { - this.delegate = requireNonNull(delegate); + super(delegate); this.minElements = minElements != null ? minElements : 0; this.maxElements = maxElements != null ? maxElements : Integer.MAX_VALUE; } @@ -53,105 +43,39 @@ final class MinMaxElementsValidation apply(final ModifiedNode modification, final Optional storeMeta, - final Version version) { - Optional ret = modification.getValidatedNode(this, storeMeta); - if (ret == null) { - // Deal with the result moving on us - ret = delegate.apply(modification, storeMeta, version); - if (ret.isPresent()) { - checkChildren(ret.get().getData()); - } - } - - return ret; - } - - @Override - void checkApplicable(final ModificationPath path, final NodeModification modification, - final Optional current, final Version version) throws DataValidationFailedException { - delegate.checkApplicable(path, modification, current, version); - - if (!(modification instanceof ModifiedNode)) { - LOG.debug("Could not validate {}, does not implement expected class {}", modification, ModifiedNode.class); - return; - } - final ModifiedNode modified = (ModifiedNode) modification; - - // We need to actually perform the operation to deal with merge in a sane manner. We know the modification - // is immutable, so the result of validation will probably not change. Note we should not be checking number - final Optional maybeApplied = delegate.apply(modified, current, version); - if (maybeApplied.isPresent()) { - // We only enforce min/max on present data and rely on MandatoryLeafEnforcer to take care of the empty case - validateMinMaxElements(path, maybeApplied.get().getData()); - } - - // Everything passed. We now have a snapshot of the result node, it would be too bad if we just threw it out. - // We know what the result of an apply operation is going to be *if* the following are kept unchanged: - // - the 'current' node - // - the schemacontext (therefore, the fact this object is associated with the modification) - // - // So let's stash the result. We will pick it up during apply operation. - modified.setValidatedNode(this, current, maybeApplied); - } - - @Override - void fullVerifyStructure(final NormalizedNode modification) { - delegate.fullVerifyStructure(modification); - checkChildren(modification); - } - - @Override - public Optional getChild(final PathArgument child) { - return delegate.getChild(child); - } - - @Override - ChildTrackingPolicy getChildPolicy() { - return delegate.getChildPolicy(); - } - - @Override - void mergeIntoModifiedNode(final ModifiedNode node, final NormalizedNode value, final Version version) { - delegate.mergeIntoModifiedNode(node, value, version); + void enforceOnData(final NormalizedNode data) { + enforceOnData(data, (actual, message) -> new IllegalArgumentException(message)); } @Override - void quickVerifyStructure(final NormalizedNode modification) { - delegate.quickVerifyStructure(modification); + void enforceOnData(final ModificationPath path, final NormalizedNode data) + throws RequiredElementCountException { + enforceOnData(data, (actual, message) -> new RequiredElementCountException(path.toInstanceIdentifier(), + minElements, maxElements, actual, message)); } - @Override - void recursivelyVerifyStructure(final NormalizedNode value) { - delegate.recursivelyVerifyStructure(value); - } - - @Override - ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return helper.add("min", minElements).add("max", maxElements).add("delegate", delegate); + @FunctionalInterface + @NonNullByDefault + interface ExceptionSupplier { + T get(int actual, String message); } - private void validateMinMaxElements(final ModificationPath path, final NormalizedNode value) - throws DataValidationFailedException { - final PathArgument id = value.getIdentifier(); + private void enforceOnData(final NormalizedNode value, + final ExceptionSupplier exceptionSupplier) throws X { final int children = numOfChildrenFromValue(value); if (minElements > children) { - throw new RequiredElementCountException(path.toInstanceIdentifier(), minElements, maxElements, children, - "%s does not have enough elements (%s), needs at least %s", id, children, minElements); + throw exceptionSupplier.get(children, value.getIdentifier() + + " does not have enough elements (" + children + "), needs at least " + minElements); } if (maxElements < children) { - throw new RequiredElementCountException(path.toInstanceIdentifier(), minElements, maxElements, children, - "%s has too many elements (%s), can have at most %s", id, children, maxElements); + throw exceptionSupplier.get(children, value.getIdentifier() + + " has too many elements (" + children + "), can have at most " + maxElements); } } - private void checkChildren(final NormalizedNode value) { - final PathArgument id = value.getIdentifier(); - final int children = numOfChildrenFromValue(value); - checkArgument(minElements <= children, "Node %s does not have enough elements (%s), needs at least %s", id, - children, minElements); - checkArgument(maxElements >= children, "Node %s has too many elements (%s), can have at most %s", id, children, - maxElements); + @Override + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return super.addToStringAttributes(helper.add("min", minElements).add("max", maxElements)); } private static int numOfChildrenFromValue(final NormalizedNode value) { diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidation.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidation.java index 8d172f5446..e21bd1ae3f 100644 --- a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidation.java +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidation.java @@ -243,7 +243,7 @@ public class ListConstraintsValidation { modificationTree.ready(); fail("Should have failed with IAE"); } catch (IllegalArgumentException e) { - assertEquals("Node (urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?" + assertEquals("(urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?" + "revision=2015-02-02)min-max-leaf-list has too many elements (4), can have at most 3", e.getMessage()); } @@ -298,7 +298,7 @@ public class ListConstraintsValidation { modificationTree.ready(); fail("Should have failed with IAE"); } catch (IllegalArgumentException e) { - assertEquals("Node (urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?" + assertEquals("(urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model?" + "revision=2015-02-02)unkeyed-list has too many elements (2), can have at most 1", e.getMessage()); } } diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/YT776Test.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/YT776Test.java index 2e8f7d7b96..d6295726a3 100644 --- a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/YT776Test.java +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/YT776Test.java @@ -114,7 +114,7 @@ public class YT776Test { mod.ready(); fail("Should fail with IAE"); } catch (IllegalArgumentException e) { - assertEquals("Node (yt776)attributes does not have enough elements (0), needs at least 1", e.getMessage()); + assertEquals("(yt776)attributes does not have enough elements (0), needs at least 1", e.getMessage()); } } @@ -166,7 +166,7 @@ public class YT776Test { mod.ready(); fail("Should fail with IAE"); } catch (IllegalArgumentException e) { - assertEquals("Node (yt776)attributes has too many elements (3), can have at most 2", e.getMessage()); + assertEquals("(yt776)attributes has too many elements (3), can have at most 2", e.getMessage()); } } -- 2.36.6