2 * Copyright (c) 2015 Cisco Systems, Inc. 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.verify;
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 java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.stream.Collectors;
20 import java.util.stream.Stream;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.opendaylight.mdsal.binding.api.DataObjectModification;
23 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
24 import org.opendaylight.yangtools.yang.binding.Augmentation;
25 import org.opendaylight.yangtools.yang.binding.ChildOf;
26 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
27 import org.opendaylight.yangtools.yang.binding.DataObject;
28 import org.opendaylight.yangtools.yang.binding.Identifiable;
29 import org.opendaylight.yangtools.yang.binding.Identifier;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
31 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
32 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
35 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * Lazily translated {@link DataObjectModification} based on {@link DataTreeCandidateNode}.
43 * {@link LazyDataObjectModification} represents Data tree change event,
44 * but whole tree is not translated or resolved eagerly, but only child nodes
45 * which are directly accessed by user of data object modification.
47 * @param <T> Type of Binding Data Object
49 final class LazyDataObjectModification<T extends DataObject> implements DataObjectModification<T> {
50 private static final Logger LOG = LoggerFactory.getLogger(LazyDataObjectModification.class);
52 private final BindingDataObjectCodecTreeNode<T> codec;
53 private final DataTreeCandidateNode domData;
54 private final PathArgument identifier;
56 private volatile List<LazyDataObjectModification<?>> childNodesCache;
57 private volatile ModificationType modificationType;
59 private LazyDataObjectModification(final BindingDataObjectCodecTreeNode<T> codec,
60 final DataTreeCandidateNode domData) {
61 this.codec = requireNonNull(codec);
62 this.domData = requireNonNull(domData);
63 identifier = codec.deserializePathArgument(domData.getIdentifier());
66 static <T extends DataObject> LazyDataObjectModification<T> create(final BindingDataObjectCodecTreeNode<T> codec,
67 final DataTreeCandidateNode domData) {
68 return new LazyDataObjectModification<>(codec, domData);
71 private static List<LazyDataObjectModification<?>> from(final BindingDataObjectCodecTreeNode<?> parentCodec,
72 final Collection<DataTreeCandidateNode> domChildNodes) {
73 final var result = new ArrayList<LazyDataObjectModification<?>>(domChildNodes.size());
74 populateList(result, parentCodec, domChildNodes);
78 private static void populateList(final List<LazyDataObjectModification<?>> result,
79 final BindingDataObjectCodecTreeNode<?> parentCodec,
80 final Collection<DataTreeCandidateNode> domChildNodes) {
81 for (var domChildNode : domChildNodes) {
82 if (domChildNode.getModificationType() != UNMODIFIED) {
83 final var type = BindingStructuralType.from(domChildNode);
84 if (type != BindingStructuralType.NOT_ADDRESSABLE) {
86 * Even if type is UNKNOWN, from perspective of BindingStructuralType we try to load codec for it.
87 * We will use that type to further specify debug log.
90 final var childCodec = parentCodec.yangPathArgumentChild(domChildNode.getIdentifier());
91 // FIXME: MDSAL-820: this no longer holds
92 verify(childCodec instanceof BindingDataObjectCodecTreeNode, "Unhandled codec %s for type %s",
94 populateList(result, type, (BindingDataObjectCodecTreeNode<?>) childCodec, domChildNode);
95 } catch (final IllegalArgumentException e) {
96 if (type == BindingStructuralType.UNKNOWN) {
97 LOG.debug("Unable to deserialize unknown DOM node {}", domChildNode, e);
99 LOG.debug("Binding representation for DOM node {} was not found", domChildNode, e);
107 private static void populateList(final List<LazyDataObjectModification<? extends DataObject>> result,
108 final BindingStructuralType type, final BindingDataObjectCodecTreeNode<?> childCodec,
109 final DataTreeCandidateNode domChildNode) {
112 // We use parent codec intentionally.
113 populateListWithSingleCodec(result, childCodec, domChildNode.getChildNodes());
115 case INVISIBLE_CONTAINER:
116 populateList(result, childCodec, domChildNode.getChildNodes());
119 case VISIBLE_CONTAINER:
120 result.add(create(childCodec, domChildNode));
126 private static void populateListWithSingleCodec(final List<LazyDataObjectModification<? extends DataObject>> result,
127 final BindingDataObjectCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
128 for (var child : childNodes) {
129 if (child.getModificationType() != UNMODIFIED) {
130 result.add(create(codec, child));
136 public T getDataBefore() {
137 return deserialize(domData.getDataBefore());
141 public T getDataAfter() {
142 return deserialize(domData.getDataAfter());
146 public Class<T> getDataType() {
147 return codec.getBindingClass();
151 public PathArgument getIdentifier() {
156 public ModificationType getModificationType() {
157 var localType = modificationType;
158 if (localType != null) {
162 modificationType = localType = switch (domData.getModificationType()) {
163 case APPEARED, WRITE -> ModificationType.WRITE;
164 case DISAPPEARED, DELETE -> ModificationType.DELETE;
165 case SUBTREE_MODIFIED -> resolveSubtreeModificationType();
167 // TODO: Should we lie about modification type instead of exception?
168 throw new IllegalStateException("Unsupported DOM Modification type " + domData.getModificationType());
173 private @NonNull ModificationType resolveSubtreeModificationType() {
174 return switch (codec.getChildAddressabilitySummary()) {
176 // All children are addressable, it is safe to report SUBTREE_MODIFIED
177 ModificationType.SUBTREE_MODIFIED;
178 case UNADDRESSABLE ->
179 // All children are non-addressable, report WRITE
180 ModificationType.WRITE;
182 // This case is not completely trivial, as we may have NOT_ADDRESSABLE nodes underneath us. If that
183 // is the case, we need to turn this modification into a WRITE operation, so that the user is able
184 // to observe those nodes being introduced. This is not efficient, but unfortunately unavoidable,
185 // as we cannot accurately represent such changes.
186 for (DataTreeCandidateNode child : domData.getChildNodes()) {
187 if (BindingStructuralType.recursiveFrom(child) == BindingStructuralType.NOT_ADDRESSABLE) {
188 // We have a non-addressable child, turn this modification into a write
189 yield ModificationType.WRITE;
193 // No unaddressable children found, proceed in addressed mode
194 yield ModificationType.SUBTREE_MODIFIED;
200 public List<LazyDataObjectModification<?>> getModifiedChildren() {
201 var local = childNodesCache;
203 childNodesCache = local = from(codec, domData.getChildNodes());
209 public <C extends ChildOf<? super T>> List<DataObjectModification<C>> getModifiedChildren(
210 final Class<C> childType) {
211 return streamModifiedChildren(childType).collect(Collectors.toList());
215 public <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>>
216 List<DataObjectModification<C>> getModifiedChildren(final Class<H> caseType, final Class<C> childType) {
217 return streamModifiedChildren(childType)
218 .filter(child -> caseType.equals(child.identifier.getCaseType().orElse(null)))
219 .collect(Collectors.toList());
222 @SuppressWarnings("unchecked")
223 private <C extends DataObject> Stream<LazyDataObjectModification<C>> streamModifiedChildren(
224 final Class<C> childType) {
225 return getModifiedChildren().stream()
226 .filter(child -> childType.isAssignableFrom(child.getDataType()))
227 .map(child -> (LazyDataObjectModification<C>) child);
231 public DataObjectModification<?> getModifiedChild(final PathArgument arg) {
232 final var domArgumentList = new ArrayList<YangInstanceIdentifier.PathArgument>();
233 final var childCodec = codec.bindingPathArgumentChild(arg, domArgumentList);
234 final var toEnter = domArgumentList.iterator();
235 var current = domData;
236 while (toEnter.hasNext() && current != null) {
237 current = current.getModifiedChild(toEnter.next()).orElse(null);
239 return current != null && current.getModificationType() != UNMODIFIED ? create(childCodec, current) : null;
243 @SuppressWarnings("unchecked")
244 public <C extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<C>> DataObjectModification<C>
245 getModifiedChildListItem(final Class<C> listItem, final K listKey) {
246 return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(listItem, listKey));
250 @SuppressWarnings("unchecked")
251 public <H extends ChoiceIn<? super T> & DataObject, C extends Identifiable<K> & ChildOf<? super H>,
252 K extends Identifier<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
253 final Class<C> listItem, final K listKey) {
254 return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(caseType, listItem, listKey));
258 @SuppressWarnings("unchecked")
259 public <C extends ChildOf<? super T>> DataObjectModification<C> getModifiedChildContainer(final Class<C> child) {
260 return (DataObjectModification<C>) getModifiedChild(Item.of(child));
264 @SuppressWarnings("unchecked")
265 public <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>> DataObjectModification<C>
266 getModifiedChildContainer(final Class<H> caseType, final Class<C> child) {
267 return (DataObjectModification<C>) getModifiedChild(Item.of(caseType, child));
271 @SuppressWarnings("unchecked")
272 public <C extends Augmentation<T> & DataObject> DataObjectModification<C> getModifiedAugmentation(
273 final Class<C> augmentation) {
274 return (DataObjectModification<C>) getModifiedChild(Item.of(augmentation));
278 public String toString() {
279 return MoreObjects.toStringHelper(this).add("identifier", identifier).add("domData", domData).toString();
282 private T deserialize(final Optional<NormalizedNode> dataAfter) {
283 return dataAfter.map(codec::deserialize).orElse(null);