/* * 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.Preconditions.checkState; import static com.google.common.base.Verify.verify; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Stopwatch; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; 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.tree.DataTreeConfiguration; import org.opendaylight.yangtools.yang.data.api.schema.tree.UniqueConstraintException; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant; import org.opendaylight.yangtools.yang.model.api.stmt.UniqueEffectiveStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link AbstractValidation} which ensures a particular {@code list} node complies with its {@code unique} * constraints. */ final class UniqueValidation extends AbstractValidation { private static final Logger LOG = LoggerFactory.getLogger(UniqueValidation.class); private final @NonNull ImmutableList> validators; private UniqueValidation(final ModificationApplyOperation delegate, final List> validators) { super(delegate); this.validators = ImmutableList.copyOf(validators); } static ModificationApplyOperation of(final ListSchemaNode schema, final DataTreeConfiguration treeConfig, final ModificationApplyOperation delegate) { final Collection uniques = schema.getUniqueConstraints(); if (!treeConfig.isUniqueIndexEnabled() || uniques.isEmpty()) { return delegate; } final Stopwatch sw = Stopwatch.createStarted(); final Map> paths = new HashMap<>(); final List> validators = uniques.stream() .map(unique -> UniqueValidator.of(unique.argument().stream() .map(descendant -> paths.computeIfAbsent(descendant, key -> toDescendantPath(schema, key))) .collect(ImmutableList.toImmutableList()))) .collect(ImmutableList.toImmutableList()); LOG.debug("Constructed {} validators in {}", validators.size(), sw); return validators.isEmpty() ? delegate : new UniqueValidation(delegate, validators); } @Override void enforceOnData(final NormalizedNode data) { enforceOnData(data, (message, values) -> new IllegalArgumentException(message)); } @Override void enforceOnData(final ModificationPath path, final NormalizedNode data) throws UniqueConstraintException { enforceOnData(data, (message, values) -> new UniqueConstraintException(path.toInstanceIdentifier(), values, message)); } private void enforceOnData(final NormalizedNode data, final ExceptionSupplier exceptionSupplier) throws T { final Stopwatch sw = Stopwatch.createStarted(); verify(data instanceof NormalizedNodeContainer, "Unexpected data %s", data); final var children = ((NormalizedNodeContainer) data).getValue(); final var collected = HashMultimap., Object>create(validators.size(), children.size()); for (NormalizedNode child : children) { verify(child instanceof DataContainerNode, "Unexpected child %s", child); final DataContainerNode cont = (DataContainerNode) child; final Map, Object> valueCache = new HashMap<>(); for (UniqueValidator validator : validators) { final Object values = validator.extractValues(valueCache, cont); final Object masked = BinaryValue.wrap(values); if (!collected.put(validator, masked)) { final Map index = validator.indexValues(values); throw exceptionSupplier.get(cont.getIdentifier() + " violates unique constraint on " + masked + " of " + index.keySet(), index); } } } LOG.trace("Enforced {} validators in {}", validators.size(), sw); } @Override ToStringHelper addToStringAttributes(final ToStringHelper helper) { return super.addToStringAttributes(helper.add("validators", validators)); } private static ImmutableList toDescendantPath(final ListSchemaNode parent, final Descendant descendant) { final List qnames = descendant.getNodeIdentifiers(); final ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(qnames.size()); final Iterator it = descendant.getNodeIdentifiers().iterator(); DataNodeContainer current = parent; while (true) { final QName qname = it.next(); final DataSchemaNode next = current.findDataChildByName(qname) .orElseThrow(() -> new IllegalStateException("Cannot find component " + qname + " of " + descendant)); builder.add(NodeIdentifier.create(qname)); if (!it.hasNext()) { checkState(next instanceof TypedDataSchemaNode, "Unexpected schema %s for %s", next, descendant); final ImmutableList ret = builder.build(); LOG.trace("Resolved {} to {}", descendant, ret); return ret; } checkState(next instanceof DataNodeContainer, "Unexpected non-container %s for %s", next, descendant); current = (DataNodeContainer) next; } } @FunctionalInterface @NonNullByDefault interface ExceptionSupplier { T get(String message, Map values); } }