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.impl.schema.tree;
10 import static com.google.common.base.Preconditions.checkState;
11 import static com.google.common.base.Verify.verify;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.base.Stopwatch;
15 import com.google.common.collect.HashMultimap;
16 import com.google.common.collect.ImmutableList;
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
30 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
31 import org.opendaylight.yangtools.yang.data.api.schema.tree.UniqueConstraintException;
32 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant;
37 import org.opendaylight.yangtools.yang.model.api.stmt.UniqueEffectiveStatement;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * A {@link AbstractValidation} which ensures a particular {@code list} node complies with its {@code unique}
45 final class UniqueValidation extends AbstractValidation {
46 private static final Logger LOG = LoggerFactory.getLogger(UniqueValidation.class);
48 private final @NonNull ImmutableList<UniqueValidator<?>> validators;
50 private UniqueValidation(final ModificationApplyOperation delegate, final List<UniqueValidator<?>> validators) {
52 this.validators = ImmutableList.copyOf(validators);
55 static ModificationApplyOperation of(final ListSchemaNode schema, final DataTreeConfiguration treeConfig,
56 final ModificationApplyOperation delegate) {
57 final Collection<? extends @NonNull UniqueEffectiveStatement> uniques = schema.getUniqueConstraints();
58 if (!treeConfig.isUniqueIndexEnabled() || uniques.isEmpty()) {
62 final Stopwatch sw = Stopwatch.createStarted();
63 final Map<Descendant, List<NodeIdentifier>> paths = new HashMap<>();
64 final List<UniqueValidator<?>> validators = uniques.stream()
65 .map(unique -> UniqueValidator.of(unique.argument().stream()
66 .map(descendant -> paths.computeIfAbsent(descendant, key -> toDescendantPath(schema, key)))
67 .collect(ImmutableList.toImmutableList())))
68 .collect(ImmutableList.toImmutableList());
69 LOG.debug("Constructed {} validators in {}", validators.size(), sw);
71 return validators.isEmpty() ? delegate : new UniqueValidation(delegate, validators);
75 void enforceOnData(final NormalizedNode<?, ?> data) {
76 enforceOnData(data, (message, values) -> new IllegalArgumentException(message));
80 void enforceOnData(final ModificationPath path, final NormalizedNode<?, ?> data)
81 throws UniqueConstraintException {
82 enforceOnData(data, (message, values) -> new UniqueConstraintException(path.toInstanceIdentifier(), values,
86 private <T extends @NonNull Exception> void enforceOnData(final NormalizedNode<?, ?> data,
87 final ExceptionSupplier<T> exceptionSupplier) throws T {
88 final Stopwatch sw = Stopwatch.createStarted();
89 verify(data instanceof NormalizedNodeContainer, "Unexpected data %s", data);
90 final var children = ((NormalizedNodeContainer<?, ?, ?>) data).getValue();
91 final var collected = HashMultimap.<UniqueValidator<?>, Object>create(validators.size(), children.size());
92 for (NormalizedNode<?, ?> child : children) {
93 verify(child instanceof DataContainerNode, "Unexpected child %s", child);
94 final DataContainerNode<?> cont = (DataContainerNode<?>) child;
96 final Map<List<NodeIdentifier>, Object> valueCache = new HashMap<>();
97 for (UniqueValidator<?> validator : validators) {
98 final Object values = validator.extractValues(valueCache, cont);
99 final Object masked = BinaryValue.wrap(values);
100 if (!collected.put(validator, masked)) {
101 final Map<Descendant, @Nullable Object> index = validator.indexValues(values);
102 throw exceptionSupplier.get(cont.getIdentifier()
103 + " violates unique constraint on " + masked + " of " + index.keySet(), index);
108 LOG.trace("Enforced {} validators in {}", validators.size(), sw);
112 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
113 return super.addToStringAttributes(helper.add("validators", validators));
116 private static ImmutableList<NodeIdentifier> toDescendantPath(final ListSchemaNode parent,
117 final Descendant descendant) {
118 final List<QName> qnames = descendant.getNodeIdentifiers();
119 final ImmutableList.Builder<NodeIdentifier> builder = ImmutableList.builderWithExpectedSize(qnames.size());
120 final Iterator<QName> it = descendant.getNodeIdentifiers().iterator();
121 DataNodeContainer current = parent;
123 final QName qname = it.next();
124 final DataSchemaNode next = current.findDataChildByName(qname)
125 .orElseThrow(() -> new IllegalStateException("Cannot find component " + qname + " of " + descendant));
126 builder.add(NodeIdentifier.create(qname));
128 checkState(next instanceof TypedDataSchemaNode, "Unexpected schema %s for %s", next, descendant);
129 final ImmutableList<NodeIdentifier> ret = builder.build();
130 LOG.trace("Resolved {} to {}", descendant, ret);
134 checkState(next instanceof DataNodeContainer, "Unexpected non-container %s for %s", next, descendant);
135 current = (DataNodeContainer) next;
141 interface ExceptionSupplier<T extends Exception> {
142 T get(String message, Map<Descendant, @Nullable Object> values);