Reduce exception guard
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / streams / listeners / AbstractWebsocketSerializer.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.streams.listeners;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
13 import java.util.ArrayDeque;
14 import java.util.Collection;
15 import java.util.Deque;
16 import java.util.Map;
17 import java.util.Set;
18 import org.opendaylight.yangtools.yang.common.QName;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
21 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
24 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
25 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
26 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
27 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
28 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
29 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
30 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 abstract class AbstractWebsocketSerializer<T extends Exception> {
35     private static final Logger LOG = LoggerFactory.getLogger(AbstractWebsocketSerializer.class);
36
37     private final EffectiveModelContext context;
38
39     AbstractWebsocketSerializer(final EffectiveModelContext context) {
40         this.context = requireNonNull(context);
41     }
42
43     public final void serialize(final DataTreeCandidate candidate, final boolean leafNodesOnly, final boolean skipData)
44             throws T {
45         if (leafNodesOnly) {
46             final Deque<PathArgument> path = new ArrayDeque<>();
47             path.addAll(candidate.getRootPath().getPathArguments());
48             serializeLeafNodesOnly(path, candidate.getRootNode(), skipData);
49         } else {
50             serializeData(candidate.getRootPath().getPathArguments(), candidate.getRootNode(), skipData);
51         }
52     }
53
54     final void serializeLeafNodesOnly(final Deque<PathArgument> path, final DataTreeCandidateNode candidate,
55             final boolean skipData) throws T {
56         NormalizedNode node = null;
57         switch (candidate.getModificationType()) {
58             case UNMODIFIED:
59                 // no reason to do anything with an unmodified node
60                 LOG.debug("DataTreeCandidate for a notification is unmodified, not serializing leaves. Candidate: {}",
61                         candidate);
62                 break;
63             case SUBTREE_MODIFIED:
64             case WRITE:
65             case APPEARED:
66                 node = candidate.getDataAfter().get();
67                 break;
68             case DELETE:
69             case DISAPPEARED:
70                 node = candidate.getDataBefore().get();
71                 break;
72             default:
73                 LOG.error("DataTreeCandidate modification has unknown type: {}", candidate.getModificationType());
74         }
75
76         if (node == null) {
77             return;
78         }
79
80         if (node instanceof LeafNode<?> || node instanceof LeafSetNode) {
81             serializeData(path, candidate, skipData);
82             return;
83         }
84
85         for (DataTreeCandidateNode childNode : candidate.getChildNodes()) {
86             path.add(childNode.getIdentifier());
87             serializeLeafNodesOnly(path, childNode, skipData);
88             path.removeLast();
89         }
90     }
91
92     private void serializeData(final Collection<PathArgument> dataPath, final DataTreeCandidateNode candidate,
93             final boolean skipData) throws T {
94         var stack = SchemaInferenceStack.of(context);
95         var current = DataSchemaContextTree.from(context).getRoot();
96         for (var arg : dataPath) {
97             final var next = verifyNotNull(current.enterChild(stack, arg),
98                 "Failed to resolve %s: cannot find %s in %s", dataPath, arg, current);
99             current = next;
100         }
101
102         // Exit to parent if needed
103         if (!stack.isEmpty()) {
104             stack.exit();
105         }
106
107         serializeData(stack.toInference(), dataPath, candidate, skipData);
108     }
109
110     abstract void serializeData(Inference parent, Collection<PathArgument> dataPath, DataTreeCandidateNode candidate,
111         boolean skipData) throws T;
112
113     abstract void serializePath(Collection<PathArgument> pathArguments) throws T;
114
115     abstract void serializeOperation(DataTreeCandidateNode candidate) throws T;
116
117     static final String convertPath(final Collection<PathArgument> path) {
118         final StringBuilder pathBuilder = new StringBuilder();
119
120         for (PathArgument pathArgument : path) {
121             if (pathArgument instanceof YangInstanceIdentifier.AugmentationIdentifier) {
122                 continue;
123             }
124             pathBuilder.append('/');
125             pathBuilder.append(pathArgument.getNodeType().getNamespace().toString().replace(':', '-'));
126             pathBuilder.append(':');
127             pathBuilder.append(pathArgument.getNodeType().getLocalName());
128
129             if (pathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
130                 pathBuilder.append("[");
131                 final Set<Map.Entry<QName, Object>> keys =
132                         ((YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument).entrySet();
133                 for (Map.Entry<QName, Object> key : keys) {
134                     pathBuilder.append(key.getKey().getNamespace().toString().replace(':', '-'));
135                     pathBuilder.append(':');
136                     pathBuilder.append(key.getKey().getLocalName());
137                     pathBuilder.append("='");
138                     pathBuilder.append(key.getValue().toString());
139                     pathBuilder.append('\'');
140                 }
141                 pathBuilder.append(']');
142             }
143         }
144
145         return pathBuilder.toString();
146     }
147
148     static final String modificationTypeToOperation(final DataTreeCandidateNode candidate,
149             final ModificationType modificationType) {
150         switch (modificationType) {
151             case UNMODIFIED:
152                 // shouldn't ever happen since the root of a modification is only triggered by some event
153                 LOG.warn("DataTreeCandidate for a notification is unmodified. Candidate: {}", candidate);
154                 return "none";
155             case SUBTREE_MODIFIED:
156             case WRITE:
157             case APPEARED:
158                 if (candidate.getDataBefore().isPresent()) {
159                     return "updated";
160                 } else {
161                     return "created";
162                 }
163             case DELETE:
164             case DISAPPEARED:
165                 return "deleted";
166             default:
167                 LOG.error("DataTreeCandidate modification has unknown type: {}", candidate.getModificationType());
168                 throw new IllegalStateException("Unknown modification type");
169         }
170     }
171 }