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