Account for reported UNMODIFIED nodes
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / LazyDataObjectModification.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.dom.adapter;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.UNMODIFIED;
12
13 import com.google.common.base.MoreObjects;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Iterator;
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.opendaylight.mdsal.binding.api.DataObjectModification;
22 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
23 import org.opendaylight.yangtools.yang.binding.Augmentation;
24 import org.opendaylight.yangtools.yang.binding.ChildOf;
25 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
26 import org.opendaylight.yangtools.yang.binding.DataObject;
27 import org.opendaylight.yangtools.yang.binding.Identifiable;
28 import org.opendaylight.yangtools.yang.binding.Identifier;
29 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
31 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Lazily translated {@link DataObjectModification} based on {@link DataTreeCandidateNode}.
40  *
41  * <p>
42  * {@link LazyDataObjectModification} represents Data tree change event,
43  * but whole tree is not translated or resolved eagerly, but only child nodes
44  * which are directly accessed by user of data object modification.
45  *
46  * @param <T> Type of Binding Data Object
47  */
48 final class LazyDataObjectModification<T extends DataObject> implements DataObjectModification<T> {
49
50     private static final Logger LOG = LoggerFactory.getLogger(LazyDataObjectModification.class);
51
52     private final BindingCodecTreeNode<T> codec;
53     private final DataTreeCandidateNode domData;
54     private final PathArgument identifier;
55
56     private volatile Collection<LazyDataObjectModification<? extends DataObject>> childNodesCache;
57     private volatile ModificationType modificationType;
58
59     private LazyDataObjectModification(final BindingCodecTreeNode<T> codec, final DataTreeCandidateNode domData) {
60         this.codec = requireNonNull(codec);
61         this.domData = requireNonNull(domData);
62         this.identifier = codec.deserializePathArgument(domData.getIdentifier());
63     }
64
65     static <T extends DataObject> LazyDataObjectModification<T> create(final BindingCodecTreeNode<T> codec,
66             final DataTreeCandidateNode domData) {
67         return new LazyDataObjectModification<>(codec, domData);
68     }
69
70     private static Collection<LazyDataObjectModification<? extends DataObject>> from(final BindingCodecTreeNode<?>
71             parentCodec, final Collection<DataTreeCandidateNode> domChildNodes) {
72         final List<LazyDataObjectModification<? extends DataObject>> result = new ArrayList<>(domChildNodes.size());
73         populateList(result, parentCodec, domChildNodes);
74         return result;
75     }
76
77     private static void populateList(final List<LazyDataObjectModification<? extends DataObject>> result,
78             final BindingCodecTreeNode<?> parentCodec, final Collection<DataTreeCandidateNode> domChildNodes) {
79         for (final DataTreeCandidateNode domChildNode : domChildNodes) {
80             if (domChildNode.getModificationType() != UNMODIFIED) {
81                 final BindingStructuralType type = BindingStructuralType.from(domChildNode);
82                 if (type != BindingStructuralType.NOT_ADDRESSABLE) {
83                     /*
84                      *  Even if type is UNKNOWN, from perspective of BindingStructuralType
85                      *  we try to load codec for it. We will use that type to further specify
86                      *  debug log.
87                      */
88                     try {
89                         final BindingCodecTreeNode<?> childCodec =
90                                 parentCodec.yangPathArgumentChild(domChildNode.getIdentifier());
91                         populateList(result, type, childCodec, domChildNode);
92                     } catch (final IllegalArgumentException e) {
93                         if (type == BindingStructuralType.UNKNOWN) {
94                             LOG.debug("Unable to deserialize unknown DOM node {}", domChildNode, e);
95                         } else {
96                             LOG.debug("Binding representation for DOM node {} was not found", domChildNode, e);
97                         }
98                     }
99                 }
100             }
101         }
102     }
103
104     private static void populateList(final List<LazyDataObjectModification<? extends DataObject>> result,
105             final BindingStructuralType type, final BindingCodecTreeNode<?> childCodec,
106             final DataTreeCandidateNode domChildNode) {
107         switch (type) {
108             case INVISIBLE_LIST:
109                 // We use parent codec intentionally.
110                 populateListWithSingleCodec(result, childCodec, domChildNode.getChildNodes());
111                 break;
112             case INVISIBLE_CONTAINER:
113                 populateList(result, childCodec, domChildNode.getChildNodes());
114                 break;
115             case UNKNOWN:
116             case VISIBLE_CONTAINER:
117                 result.add(create(childCodec, domChildNode));
118                 break;
119             default:
120         }
121     }
122
123     private static void populateListWithSingleCodec(final List<LazyDataObjectModification<? extends DataObject>> result,
124             final BindingCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
125         for (final DataTreeCandidateNode child : childNodes) {
126             if (child.getModificationType() != UNMODIFIED) {
127                 result.add(create(codec, child));
128             }
129         }
130     }
131
132     @Override
133     public T getDataBefore() {
134         return deserialize(domData.getDataBefore());
135     }
136
137     @Override
138     public T getDataAfter() {
139         return deserialize(domData.getDataAfter());
140     }
141
142     @Override
143     public Class<T> getDataType() {
144         return codec.getBindingClass();
145     }
146
147     @Override
148     public PathArgument getIdentifier() {
149         return identifier;
150     }
151
152     @Override
153     public ModificationType getModificationType() {
154         ModificationType localType = modificationType;
155         if (localType != null) {
156             return localType;
157         }
158
159         switch (domData.getModificationType()) {
160             case APPEARED:
161             case WRITE:
162                 localType = ModificationType.WRITE;
163                 break;
164             case DISAPPEARED:
165             case DELETE:
166                 localType = ModificationType.DELETE;
167                 break;
168             case SUBTREE_MODIFIED:
169                 localType = resolveSubtreeModificationType();
170                 break;
171             default:
172                 // TODO: Should we lie about modification type instead of exception?
173                 throw new IllegalStateException("Unsupported DOM Modification type " + domData.getModificationType());
174         }
175
176         modificationType = localType;
177         return localType;
178     }
179
180     private ModificationType resolveSubtreeModificationType() {
181         switch (codec.getChildAddressabilitySummary()) {
182             case ADDRESSABLE:
183                 // All children are addressable, it is safe to report SUBTREE_MODIFIED
184                 return ModificationType.SUBTREE_MODIFIED;
185             case UNADDRESSABLE:
186                 // All children are non-addressable, report WRITE
187                 return ModificationType.WRITE;
188             case MIXED:
189                 // This case is not completely trivial, as we may have NOT_ADDRESSABLE nodes underneath us. If that
190                 // is the case, we need to turn this modification into a WRITE operation, so that the user is able
191                 // to observe those nodes being introduced. This is not efficient, but unfortunately unavoidable,
192                 // as we cannot accurately represent such changes.
193                 for (DataTreeCandidateNode child : domData.getChildNodes()) {
194                     if (BindingStructuralType.recursiveFrom(child) == BindingStructuralType.NOT_ADDRESSABLE) {
195                         // We have a non-addressable child, turn this modification into a write
196                         return ModificationType.WRITE;
197                     }
198                 }
199
200                 // No unaddressable children found, proceed in addressed mode
201                 return ModificationType.SUBTREE_MODIFIED;
202             default:
203                 throw new IllegalStateException("Unsupported child addressability summary "
204                         + codec.getChildAddressabilitySummary());
205         }
206     }
207
208     @Override
209     public Collection<LazyDataObjectModification<? extends DataObject>> getModifiedChildren() {
210         Collection<LazyDataObjectModification<? extends DataObject>> local = childNodesCache;
211         if (local == null) {
212             childNodesCache = local = from(codec, domData.getChildNodes());
213         }
214         return local;
215     }
216
217     @Override
218     public <C extends ChildOf<? super T>> Collection<DataObjectModification<C>>
219             getModifiedChildren(final Class<C> childType) {
220         return streamModifiedChildren(childType).collect(Collectors.toList());
221     }
222
223     @Override
224     public <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>>
225             Collection<DataObjectModification<C>> getModifiedChildren(final Class<H> caseType,
226                     final Class<C> childType) {
227         return streamModifiedChildren(childType)
228                 .filter(child -> caseType.equals(child.identifier.getCaseType().orElse(null)))
229                 .collect(Collectors.toList());
230     }
231
232     @SuppressWarnings("unchecked")
233     private <C extends DataObject> Stream<LazyDataObjectModification<C>> streamModifiedChildren(
234             final Class<C> childType) {
235         return getModifiedChildren().stream()
236                 .filter(child -> childType.isAssignableFrom(child.getDataType()))
237                 .map(child -> (LazyDataObjectModification<C>) child);
238     }
239
240     @Override
241     public DataObjectModification<? extends DataObject> getModifiedChild(final PathArgument arg) {
242         final List<YangInstanceIdentifier.PathArgument> domArgumentList = new ArrayList<>();
243         final BindingCodecTreeNode<?> childCodec = codec.bindingPathArgumentChild(arg, domArgumentList);
244         final Iterator<YangInstanceIdentifier.PathArgument> toEnter = domArgumentList.iterator();
245         DataTreeCandidateNode current = domData;
246         while (toEnter.hasNext() && current != null) {
247             current = current.getModifiedChild(toEnter.next());
248         }
249         return current != null && current.getModificationType() != UNMODIFIED ? create(childCodec, current) : null;
250     }
251
252     @Override
253     @SuppressWarnings("unchecked")
254     public <C extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<C>> DataObjectModification<C>
255             getModifiedChildListItem(final Class<C> listItem, final K listKey) {
256         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(listItem, listKey));
257     }
258
259     @Override
260     @SuppressWarnings("unchecked")
261     public <H extends ChoiceIn<? super T> & DataObject, C extends Identifiable<K> & ChildOf<? super H>,
262             K extends Identifier<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
263                     final Class<C> listItem, final K listKey) {
264         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(caseType, listItem, listKey));
265     }
266
267     @Override
268     @SuppressWarnings("unchecked")
269     public <C extends ChildOf<? super T>> DataObjectModification<C> getModifiedChildContainer(final Class<C> child) {
270         return (DataObjectModification<C>) getModifiedChild(Item.of(child));
271     }
272
273     @Override
274     @SuppressWarnings("unchecked")
275     public <H extends ChoiceIn<? super T> & DataObject, C extends ChildOf<? super H>> DataObjectModification<C>
276             getModifiedChildContainer(final Class<H> caseType, final Class<C> child) {
277         return (DataObjectModification<C>) getModifiedChild(Item.of(caseType, child));
278     }
279
280     @Override
281     @SuppressWarnings("unchecked")
282     public <C extends Augmentation<T> & DataObject> DataObjectModification<C> getModifiedAugmentation(
283             final Class<C> augmentation) {
284         return (DataObjectModification<C>) getModifiedChild(Item.of(augmentation));
285     }
286
287     @Override
288     public String toString() {
289         return MoreObjects.toStringHelper(this).add("identifier", identifier).add("domData", domData).toString();
290     }
291
292     private T deserialize(final Optional<NormalizedNode<?, ?>> dataAfter) {
293         return dataAfter.map(codec::deserialize).orElse(null);
294     }
295 }