2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.data.tree.impl;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.base.Stopwatch;
15 import com.google.common.base.VerifyException;
16 import com.google.common.collect.HashMultimap;
17 import com.google.common.collect.ImmutableList;
18 import java.util.HashMap;
19 import java.util.List;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
28 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
29 import org.opendaylight.yangtools.yang.data.tree.api.UniqueConstraintException;
30 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
31 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * A {@link AbstractValidation} which ensures a particular {@code list} node complies with its {@code unique}
41 final class UniqueValidation extends AbstractValidation {
42 private static final Logger LOG = LoggerFactory.getLogger(UniqueValidation.class);
44 private final @NonNull ImmutableList<UniqueValidator<?>> validators;
46 private UniqueValidation(final ModificationApplyOperation delegate,
47 final ImmutableList<UniqueValidator<?>> validators) {
49 this.validators = requireNonNull(validators);
52 static ModificationApplyOperation of(final ListSchemaNode schema, final DataTreeConfiguration treeConfig,
53 final ModificationApplyOperation delegate) {
54 final ImmutableList<UniqueValidator<?>> validators = validatorsOf(schema, treeConfig);
55 return validators.isEmpty() ? delegate : new UniqueValidation(delegate, validators);
58 static ImmutableList<UniqueValidator<?>> validatorsOf(final ListSchemaNode schema,
59 final DataTreeConfiguration treeConfig) {
60 final var uniques = schema.getUniqueConstraints();
61 if (!treeConfig.isUniqueIndexEnabled() || uniques.isEmpty()) {
62 return ImmutableList.of();
65 final var sw = Stopwatch.createStarted();
66 final var paths = new HashMap<Descendant, List<NodeIdentifier>>();
67 final var validators = uniques.stream()
68 .map(unique -> UniqueValidator.of(unique.argument().stream()
69 .map(descendant -> paths.computeIfAbsent(descendant, key -> toDescendantPath(schema, key)))
70 .collect(ImmutableList.toImmutableList())))
71 .collect(ImmutableList.<UniqueValidator<?>>toImmutableList());
72 LOG.debug("Constructed {} validators in {}", validators.size(), sw);
77 void enforceOnData(final NormalizedNode data) {
78 enforceOnData(data, (message, values) -> new UniqueValidationFailedException(message));
82 void enforceOnData(final ModificationPath path, final NormalizedNode data) throws UniqueConstraintException {
83 enforceOnData(data, (message, values) -> new UniqueConstraintException(path.toInstanceIdentifier(), values,
87 private <T extends @NonNull Exception> void enforceOnData(final NormalizedNode data,
88 final ExceptionSupplier<T> exceptionSupplier) throws T {
89 final var sw = Stopwatch.createStarted();
90 if (!(data instanceof NormalizedNodeContainer<?> dataContainer)) {
91 throw new VerifyException("Unexpected data " + data.prettyTree());
94 final var children = dataContainer.body();
95 final var collected = HashMultimap.<UniqueValidator<?>, Object>create(validators.size(), children.size());
96 for (var child : children) {
97 if (!(child instanceof DataContainerNode cont)) {
98 throw new VerifyException("Unexpected child " + child);
101 final var valueCache = new HashMap<List<NodeIdentifier>, Object>();
102 for (var validator : validators) {
103 final Object values = validator.extractValues(valueCache, cont);
104 final Object masked = BinaryValue.wrap(values);
105 if (!collected.put(validator, masked)) {
106 final var index = validator.indexValues(values);
107 throw exceptionSupplier.get(cont.name()
108 + " violates unique constraint on " + masked + " of " + index.keySet(), index);
113 LOG.trace("Enforced {} validators in {}", validators.size(), sw);
117 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
118 return super.addToStringAttributes(helper.add("validators", validators));
121 private static ImmutableList<NodeIdentifier> toDescendantPath(final ListSchemaNode parent,
122 final Descendant descendant) {
123 final var qnames = descendant.getNodeIdentifiers();
124 final var builder = ImmutableList.<NodeIdentifier>builderWithExpectedSize(qnames.size());
125 final var it = descendant.getNodeIdentifiers().iterator();
126 DataNodeContainer current = parent;
128 final var qname = it.next();
129 final var next = current.findDataChildByName(qname)
130 .orElseThrow(() -> new IllegalStateException("Cannot find component " + qname + " of " + descendant));
131 builder.add(NodeIdentifier.create(qname));
133 checkState(next instanceof TypedDataSchemaNode, "Unexpected schema %s for %s", next, descendant);
134 final var ret = builder.build();
135 LOG.trace("Resolved {} to {}", descendant, ret);
139 checkState(next instanceof DataNodeContainer, "Unexpected non-container %s for %s", next, descendant);
140 current = (DataNodeContainer) next;
146 interface ExceptionSupplier<T extends Exception> {
147 T get(String message, Map<Descendant, @Nullable Object> values);