2 * Copyright (c) 2023 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.mdsal.binding.dom.adapter;
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.yangtools.yang.data.tree.api.ModificationType.UNMODIFIED;
14 import com.google.common.base.MoreObjects;
15 import com.google.common.base.MoreObjects.ToStringHelper;
16 import com.google.common.base.VerifyException;
17 import com.google.common.collect.ArrayListMultimap;
18 import com.google.common.collect.ImmutableList;
19 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
20 import java.lang.invoke.MethodHandles;
21 import java.lang.invoke.VarHandle;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.opendaylight.mdsal.binding.api.DataObjectModification;
30 import org.opendaylight.mdsal.binding.dom.codec.api.BindingAugmentationCodecTreeNode;
31 import org.opendaylight.mdsal.binding.dom.codec.api.BindingChoiceCodecTreeNode;
32 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataContainerCodecTreeNode;
33 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
34 import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode;
35 import org.opendaylight.yangtools.yang.binding.Augmentation;
36 import org.opendaylight.yangtools.yang.binding.ChildOf;
37 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
38 import org.opendaylight.yangtools.yang.binding.DataObject;
39 import org.opendaylight.yangtools.yang.binding.ExactDataObjectStep;
40 import org.opendaylight.yangtools.yang.binding.Key;
41 import org.opendaylight.yangtools.yang.binding.KeyAware;
42 import org.opendaylight.yangtools.yang.binding.KeyStep;
43 import org.opendaylight.yangtools.yang.binding.NodeStep;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
46 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * Lazily translated {@link DataObjectModification} based on {@link DataTreeCandidateNode}.
52 * {@link AbstractDataObjectModification} represents Data tree change event, but whole tree is not translated or
53 * resolved eagerly, but only child nodes which are directly accessed by user of data object modification.
56 * This class is further specialized as {@link LazyAugmentationModification} and {@link LazyDataObjectModification}, as
57 * both use different serialization methods.
59 * @param <T> Type of Binding {@link DataObject}
60 * @param <N> Type of underlying {@link CommonDataObjectCodecTreeNode}
62 abstract sealed class AbstractDataObjectModification<T extends DataObject, N extends CommonDataObjectCodecTreeNode<T>>
63 implements DataObjectModification<T>
64 permits LazyAugmentationModification, LazyDataObjectModification {
65 private static final Logger LOG = LoggerFactory.getLogger(AbstractDataObjectModification.class);
66 private static final @NonNull Object NULL_DATA_OBJECT = new Object();
67 private static final VarHandle MODIFICATION_TYPE;
68 private static final VarHandle MODIFIED_CHILDREN;
69 private static final VarHandle DATA_BEFORE;
70 private static final VarHandle DATA_AFTER;
73 final var lookup = MethodHandles.lookup();
76 MODIFICATION_TYPE = lookup.findVarHandle(AbstractDataObjectModification.class, "modificationType",
77 ModificationType.class);
78 MODIFIED_CHILDREN = lookup.findVarHandle(AbstractDataObjectModification.class, "modifiedChildren",
80 DATA_BEFORE = lookup.findVarHandle(AbstractDataObjectModification.class, "dataBefore", Object.class);
81 DATA_AFTER = lookup.findVarHandle(AbstractDataObjectModification.class, "dataAfter", Object.class);
82 } catch (NoSuchFieldException | IllegalAccessException e) {
83 throw new ExceptionInInitializerError(e);
87 final @NonNull DataTreeCandidateNode domData;
88 final @NonNull ExactDataObjectStep<T> step;
89 final @NonNull N codec;
91 @SuppressWarnings("unused")
92 @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
93 private volatile ImmutableList<AbstractDataObjectModification<?, ?>> modifiedChildren;
94 @SuppressWarnings("unused")
95 @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
96 private volatile ModificationType modificationType;
97 @SuppressWarnings("unused")
98 @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
99 private volatile Object dataBefore;
100 @SuppressWarnings("unused")
101 @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
102 private volatile Object dataAfter;
104 AbstractDataObjectModification(final DataTreeCandidateNode domData, final N codec,
105 final ExactDataObjectStep<T> step) {
106 this.domData = requireNonNull(domData);
107 this.step = requireNonNull(step);
108 this.codec = requireNonNull(codec);
111 static @Nullable AbstractDataObjectModification<?, ?> from(final CommonDataObjectCodecTreeNode<?> codec,
112 final @NonNull DataTreeCandidateNode current) {
113 if (codec instanceof BindingDataObjectCodecTreeNode<?> childDataObjectCodec) {
114 return new LazyDataObjectModification<>(childDataObjectCodec, current);
115 } else if (codec instanceof BindingAugmentationCodecTreeNode<?> childAugmentationCodec) {
116 return LazyAugmentationModification.forParent(childAugmentationCodec, current);
118 throw new VerifyException("Unhandled codec " + codec);
123 public final ExactDataObjectStep<T> step() {
128 public final ModificationType modificationType() {
129 final var local = (ModificationType) MODIFICATION_TYPE.getAcquire(this);
130 return local != null ? local : loadModificationType();
133 private @NonNull ModificationType loadModificationType() {
134 final var domModificationType = domModificationType();
135 final var computed = switch (domModificationType) {
136 case APPEARED, WRITE -> ModificationType.WRITE;
137 case DISAPPEARED, DELETE -> ModificationType.DELETE;
138 case SUBTREE_MODIFIED -> resolveSubtreeModificationType();
140 // TODO: Should we lie about modification type instead of exception?
141 throw new IllegalStateException("Unsupported DOM Modification type " + domModificationType);
144 MODIFICATION_TYPE.setRelease(this, computed);
149 public final T dataBefore() {
150 final var local = DATA_BEFORE.getAcquire(this);
151 return local != null ? unmask(local) : loadDataBefore();
154 private @Nullable T loadDataBefore() {
155 final var computed = deserializeNullable(domData.dataBefore());
156 final var witness = DATA_BEFORE.compareAndExchangeRelease(this, null, mask(computed));
157 return witness == null ? computed : unmask(witness);
161 public final T dataAfter() {
162 final var local = DATA_AFTER.getAcquire(this);
163 return local != null ? unmask(local) : loadDataAfter();
166 private @Nullable T loadDataAfter() {
167 final var computed = deserializeNullable(domData.dataAfter());
168 final var witness = DATA_AFTER.compareAndExchangeRelease(this, null, mask(computed));
169 return witness == null ? computed : unmask(witness);
172 private static <T extends DataObject> @NonNull Object mask(final @Nullable T obj) {
173 return obj != null ? obj : NULL_DATA_OBJECT;
176 @SuppressWarnings("unchecked")
177 private @Nullable T unmask(final @NonNull Object obj) {
178 return obj == NULL_DATA_OBJECT ? null : (T) verifyNotNull(obj);
181 private @Nullable T deserializeNullable(final @Nullable NormalizedNode normalized) {
182 return normalized == null ? null : deserialize(normalized);
185 abstract @Nullable T deserialize(@NonNull NormalizedNode normalized);
188 public final DataObjectModification<?> getModifiedChild(final ExactDataObjectStep<?> arg) {
189 final var domArgumentList = new ArrayList<YangInstanceIdentifier.PathArgument>();
190 final var childCodec = codec.bindingPathArgumentChild(arg, domArgumentList);
191 final var toEnter = domArgumentList.iterator();
193 // Careful now: we need to validated the first item against subclass
194 var current = toEnter.hasNext() ? firstModifiedChild(toEnter.next()) : domData;
195 // ... and for everything else we can just go wild
196 while (toEnter.hasNext() && current != null) {
197 current = current.modifiedChild(toEnter.next());
200 if (current == null || current.modificationType() == UNMODIFIED) {
203 return from(childCodec, current);
206 abstract @Nullable DataTreeCandidateNode firstModifiedChild(YangInstanceIdentifier.PathArgument arg);
209 public final ImmutableList<AbstractDataObjectModification<?, ?>> modifiedChildren() {
210 final var local = (ImmutableList<AbstractDataObjectModification<?, ?>>) MODIFIED_CHILDREN.getAcquire(this);
211 return local != null ? local : loadModifiedChilden();
215 public final <C extends ChildOf<? super T>> List<DataObjectModification<C>> getModifiedChildren(
216 final Class<C> childType) {
217 return streamModifiedChildren(childType).collect(Collectors.toList());
221 public final <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>>
222 List<DataObjectModification<C>> getModifiedChildren(final Class<H> caseType, final Class<C> childType) {
223 return streamModifiedChildren(childType)
224 .filter(child -> caseType.equals(child.step.caseType()))
225 .collect(Collectors.toList());
228 @SuppressWarnings("unchecked")
229 private @NonNull ImmutableList<AbstractDataObjectModification<?, ?>> loadModifiedChilden() {
230 final var builder = ImmutableList.<AbstractDataObjectModification<?, ?>>builder();
231 populateList(builder, codec, domData, domChildNodes());
232 final var computed = builder.build();
233 // Non-trivial return: use CAS to ensure we reuse concurrent loads
234 final var witness = MODIFIED_CHILDREN.compareAndExchangeRelease(this, null, computed);
235 return witness == null ? computed : (ImmutableList<AbstractDataObjectModification<?, ?>>) witness;
238 @SuppressWarnings("unchecked")
239 private <C extends DataObject> Stream<LazyDataObjectModification<C>> streamModifiedChildren(
240 final Class<C> childType) {
241 return getModifiedChildren().stream()
242 .filter(child -> childType.isAssignableFrom(child.dataType()))
243 .map(child -> (LazyDataObjectModification<C>) child);
247 @SuppressWarnings("unchecked")
248 public final <C extends KeyAware<K> & ChildOf<? super T>, K extends Key<C>> DataObjectModification<C>
249 getModifiedChildListItem(final Class<C> listItem, final K listKey) {
250 return (DataObjectModification<C>) getModifiedChild(new KeyStep<>(listItem, listKey));
254 @SuppressWarnings("unchecked")
255 public final <H extends ChoiceIn<? super T> & DataObject, C extends KeyAware<K> & ChildOf<? super H>,
256 K extends Key<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
257 final Class<C> listItem, final K listKey) {
258 return (DataObjectModification<C>) getModifiedChild(new KeyStep<>(listItem, caseType, listKey));
262 @SuppressWarnings("unchecked")
263 public final <C extends ChildOf<? super T>> DataObjectModification<C> getModifiedChildContainer(
264 final Class<C> child) {
265 return (DataObjectModification<C>) getModifiedChild(new NodeStep<>(child));
269 @SuppressWarnings("unchecked")
270 public final <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>> DataObjectModification<C>
271 getModifiedChildContainer(final Class<H> caseType, final Class<C> child) {
272 return (DataObjectModification<C>) getModifiedChild(new NodeStep<>(caseType, child));
276 @SuppressWarnings("unchecked")
277 public final <C extends Augmentation<T> & DataObject> DataObjectModification<C> getModifiedAugmentation(
278 final Class<C> augmentation) {
279 return (DataObjectModification<C>) getModifiedChild(new NodeStep<>(augmentation));
283 public final String toString() {
284 return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
287 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
288 return helper.add("step", step).add("domData", domData);
291 abstract @NonNull Collection<DataTreeCandidateNode> domChildNodes();
293 abstract org.opendaylight.yangtools.yang.data.tree.api.@NonNull ModificationType domModificationType();
295 private @NonNull ModificationType resolveSubtreeModificationType() {
296 return switch (codec.getChildAddressabilitySummary()) {
298 // All children are addressable, it is safe to report SUBTREE_MODIFIED
299 ModificationType.SUBTREE_MODIFIED;
300 case UNADDRESSABLE ->
301 // All children are non-addressable, report WRITE
302 ModificationType.WRITE;
304 // This case is not completely trivial, as we may have NOT_ADDRESSABLE nodes underneath us. If that
305 // is the case, we need to turn this modification into a WRITE operation, so that the user is able
306 // to observe those nodes being introduced. This is not efficient, but unfortunately unavoidable,
307 // as we cannot accurately represent such changes.
308 for (var child : domChildNodes()) {
309 if (BindingStructuralType.recursiveFrom(child) == BindingStructuralType.NOT_ADDRESSABLE) {
310 // We have a non-addressable child, turn this modification into a write
311 yield ModificationType.WRITE;
315 // No unaddressable children found, proceed in addressed mode
316 yield ModificationType.SUBTREE_MODIFIED;
321 private static void populateList(final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
322 final BindingDataContainerCodecTreeNode<?> parentCodec, final DataTreeCandidateNode parent,
323 final Collection<DataTreeCandidateNode> children) {
324 final var augmentChildren =
325 ArrayListMultimap.<BindingAugmentationCodecTreeNode<?>, DataTreeCandidateNode>create();
327 for (var domChildNode : parent.childNodes()) {
328 if (domChildNode.modificationType() != UNMODIFIED) {
329 final var type = BindingStructuralType.from(domChildNode);
330 if (type != BindingStructuralType.NOT_ADDRESSABLE) {
332 * Even if type is UNKNOWN, from perspective of BindingStructuralType we try to load codec for it.
333 * We will use that type to further specify debug log.
336 final var childCodec = parentCodec.yangPathArgumentChild(domChildNode.name());
337 if (childCodec instanceof BindingDataObjectCodecTreeNode<?> childDataObjectCodec) {
338 populateList(result, type, childDataObjectCodec, domChildNode);
339 } else if (childCodec instanceof BindingAugmentationCodecTreeNode<?> childAugmentationCodec) {
340 // Defer creation once we have collected all modified children
341 augmentChildren.put(childAugmentationCodec, domChildNode);
342 } else if (childCodec instanceof BindingChoiceCodecTreeNode<?> childChoiceCodec) {
343 populateList(result, childChoiceCodec, domChildNode, domChildNode.childNodes());
345 throw new VerifyException("Unhandled codec %s for type %s".formatted(childCodec, type));
347 } catch (final IllegalArgumentException e) {
348 if (type == BindingStructuralType.UNKNOWN) {
349 LOG.debug("Unable to deserialize unknown DOM node {}", domChildNode, e);
351 LOG.debug("Binding representation for DOM node {} was not found", domChildNode, e);
358 for (var entry : augmentChildren.asMap().entrySet()) {
359 final var modification = LazyAugmentationModification.forModifications(entry.getKey(), parent,
361 if (modification != null) {
362 result.add(modification);
367 private static void populateList(final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
368 final BindingStructuralType type, final BindingDataObjectCodecTreeNode<?> childCodec,
369 final DataTreeCandidateNode domChildNode) {
372 // We use parent codec intentionally.
373 populateListWithSingleCodec(result, childCodec, domChildNode.childNodes());
375 case INVISIBLE_CONTAINER:
376 populateList(result, childCodec, domChildNode, domChildNode.childNodes());
379 case VISIBLE_CONTAINER:
380 result.add(new LazyDataObjectModification<>(childCodec, domChildNode));
386 private static void populateListWithSingleCodec(
387 final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
388 final BindingDataObjectCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
389 for (var child : childNodes) {
390 if (child.modificationType() != UNMODIFIED) {
391 result.add(new LazyDataObjectModification<>(codec, child));