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;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.base.Stopwatch;
16 import com.google.common.collect.HashMultimap;
17 import com.google.common.collect.ImmutableList;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.List;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
32 import org.opendaylight.yangtools.yang.data.api.schema.tree.UniqueConstraintException;
33 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
34 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant;
38 import org.opendaylight.yangtools.yang.model.api.stmt.UniqueEffectiveStatement;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * A {@link AbstractValidation} which ensures a particular {@code list} node complies with its {@code unique}
46 final class UniqueValidation extends AbstractValidation {
47 private static final Logger LOG = LoggerFactory.getLogger(UniqueValidation.class);
49 private final @NonNull ImmutableList<UniqueValidator<?>> validators;
51 private UniqueValidation(final ModificationApplyOperation delegate,
52 final ImmutableList<UniqueValidator<?>> validators) {
54 this.validators = requireNonNull(validators);
57 static ModificationApplyOperation of(final ListSchemaNode schema, final DataTreeConfiguration treeConfig,
58 final ModificationApplyOperation delegate) {
59 final ImmutableList<UniqueValidator<?>> validators = validatorsOf(schema, treeConfig);
60 return validators.isEmpty() ? delegate : new UniqueValidation(delegate, validators);
63 static ImmutableList<UniqueValidator<?>> validatorsOf(final ListSchemaNode schema,
64 final DataTreeConfiguration treeConfig) {
65 final Collection<? extends @NonNull UniqueEffectiveStatement> uniques = schema.getUniqueConstraints();
66 if (!treeConfig.isUniqueIndexEnabled() || uniques.isEmpty()) {
67 return ImmutableList.of();
70 final Stopwatch sw = Stopwatch.createStarted();
71 final Map<Descendant, List<NodeIdentifier>> paths = new HashMap<>();
72 final ImmutableList<UniqueValidator<?>> validators = uniques.stream()
73 .map(unique -> UniqueValidator.of(unique.argument().stream()
74 .map(descendant -> paths.computeIfAbsent(descendant, key -> toDescendantPath(schema, key)))
75 .collect(ImmutableList.toImmutableList())))
76 .collect(ImmutableList.toImmutableList());
77 LOG.debug("Constructed {} validators in {}", validators.size(), sw);
83 void enforceOnData(final NormalizedNode<?, ?> data) {
84 enforceOnData(data, (message, values) -> new UniqueValidationFailedException(message));
88 void enforceOnData(final ModificationPath path, final NormalizedNode<?, ?> data)
89 throws UniqueConstraintException {
90 enforceOnData(data, (message, values) -> new UniqueConstraintException(path.toInstanceIdentifier(), values,
94 private <T extends @NonNull Exception> void enforceOnData(final NormalizedNode<?, ?> data,
95 final ExceptionSupplier<T> exceptionSupplier) throws T {
96 final Stopwatch sw = Stopwatch.createStarted();
97 verify(data instanceof NormalizedNodeContainer, "Unexpected data %s", data);
98 final var children = ((NormalizedNodeContainer<?, ?, ?>) data).getValue();
99 final var collected = HashMultimap.<UniqueValidator<?>, Object>create(validators.size(), children.size());
100 for (NormalizedNode<?, ?> child : children) {
101 verify(child instanceof DataContainerNode, "Unexpected child %s", child);
102 final DataContainerNode<?> cont = (DataContainerNode<?>) child;
104 final Map<List<NodeIdentifier>, Object> valueCache = new HashMap<>();
105 for (UniqueValidator<?> validator : validators) {
106 final Object values = validator.extractValues(valueCache, cont);
107 final Object masked = BinaryValue.wrap(values);
108 if (!collected.put(validator, masked)) {
109 final Map<Descendant, @Nullable Object> index = validator.indexValues(values);
110 throw exceptionSupplier.get(cont.getIdentifier()
111 + " violates unique constraint on " + masked + " of " + index.keySet(), index);
116 LOG.trace("Enforced {} validators in {}", validators.size(), sw);
120 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
121 return super.addToStringAttributes(helper.add("validators", validators));
124 private static ImmutableList<NodeIdentifier> toDescendantPath(final ListSchemaNode parent,
125 final Descendant descendant) {
126 final List<QName> qnames = descendant.getNodeIdentifiers();
127 final ImmutableList.Builder<NodeIdentifier> builder = ImmutableList.builderWithExpectedSize(qnames.size());
128 final Iterator<QName> it = descendant.getNodeIdentifiers().iterator();
129 DataNodeContainer current = parent;
131 final QName qname = it.next();
132 final DataSchemaNode next = current.findDataChildByName(qname)
133 .orElseThrow(() -> new IllegalStateException("Cannot find component " + qname + " of " + descendant));
134 builder.add(NodeIdentifier.create(qname));
136 checkState(next instanceof TypedDataSchemaNode, "Unexpected schema %s for %s", next, descendant);
137 final ImmutableList<NodeIdentifier> ret = builder.build();
138 LOG.trace("Resolved {} to {}", descendant, ret);
142 checkState(next instanceof DataNodeContainer, "Unexpected non-container %s for %s", next, descendant);
143 current = (DataNodeContainer) next;
149 interface ExceptionSupplier<T extends Exception> {
150 T get(String message, Map<Descendant, @Nullable Object> values);