Merge "Choice and case resolving in JSON output"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / rest / impl / JsonMapper.java
1 package org.opendaylight.controller.sal.rest.impl;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4
5 import java.io.IOException;
6 import java.util.*;
7
8 import javax.activation.UnsupportedDataTypeException;
9
10 import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
11 import org.opendaylight.yangtools.yang.data.api.*;
12 import org.opendaylight.yangtools.yang.model.api.*;
13 import org.opendaylight.yangtools.yang.model.api.type.*;
14
15 import com.google.common.base.Preconditions;
16 import com.google.gson.stream.JsonWriter;
17
18 class JsonMapper {
19
20     private final Set<LeafListSchemaNode> foundLeafLists = new HashSet<>();
21     private final Set<ListSchemaNode> foundLists = new HashSet<>();
22
23     public void write(JsonWriter writer, CompositeNode data, DataNodeContainer schema) throws IOException {
24         Preconditions.checkNotNull(writer);
25         Preconditions.checkNotNull(data);
26         Preconditions.checkNotNull(schema);
27
28         writer.beginObject();
29
30         if (schema instanceof ContainerSchemaNode) {
31             writeContainer(writer, data, (ContainerSchemaNode) schema);
32         } else if (schema instanceof ListSchemaNode) {
33             writeList(writer, null, data, (ListSchemaNode) schema);
34         } else {
35             throw new UnsupportedDataTypeException(
36                     "Schema can be ContainerSchemaNode or ListSchemaNode. Other types are not supported yet.");
37         }
38
39         writer.endObject();
40
41         foundLeafLists.clear();
42         foundLists.clear();
43     }
44
45     private void writeChildrenOfParent(JsonWriter writer, CompositeNode parent, DataNodeContainer parentSchema)
46             throws IOException {
47         checkNotNull(parent);
48         checkNotNull(parentSchema);
49
50         List<String> longestPathToElementViaChoiceCase = new ArrayList<>();
51         for (Node<?> child : parent.getChildren()) {
52             Deque<String> choiceCasePathStack = new ArrayDeque<>(longestPathToElementViaChoiceCase);
53             SchemaLocation schemaLocation = findFirstSchemaForNode(child, parentSchema.getChildNodes(),
54                     choiceCasePathStack);
55
56             if (schemaLocation == null) {
57                 if (!choiceCasePathStack.isEmpty()) {
58                     throw new UnsupportedDataTypeException("On choice-case path " + choiceCasePathStack
59                             + " wasn't found data schema for " + child.getNodeType().getLocalName());
60                 } else {
61                     throw new UnsupportedDataTypeException("Probably the data node \""
62                             + child.getNodeType().getLocalName() + "\" is not conform to schema");
63                 }
64             }
65
66             longestPathToElementViaChoiceCase = resolveLongerPath(longestPathToElementViaChoiceCase,
67                     schemaLocation.getLocation());
68
69             DataSchemaNode childSchema = schemaLocation.getSchema();
70
71             if (childSchema instanceof ContainerSchemaNode) {
72                 Preconditions.checkState(child instanceof CompositeNode,
73                         "Data representation of Container should be CompositeNode - " + child.getNodeType());
74                 writeContainer(writer, (CompositeNode) child, (ContainerSchemaNode) childSchema);
75             } else if (childSchema instanceof ListSchemaNode) {
76                 if (!foundLists.contains(childSchema)) {
77                     Preconditions.checkState(child instanceof CompositeNode,
78                             "Data representation of List should be CompositeNode - " + child.getNodeType());
79                     foundLists.add((ListSchemaNode) childSchema);
80                     writeList(writer, parent, (CompositeNode) child, (ListSchemaNode) childSchema);
81                 }
82             } else if (childSchema instanceof LeafListSchemaNode) {
83                 if (!foundLeafLists.contains(childSchema)) {
84                     Preconditions.checkState(child instanceof SimpleNode<?>,
85                             "Data representation of LeafList should be SimpleNode - " + child.getNodeType());
86                     foundLeafLists.add((LeafListSchemaNode) childSchema);
87                     writeLeafList(writer, parent, (SimpleNode<?>) child, (LeafListSchemaNode) childSchema);
88                 }
89             } else if (childSchema instanceof LeafSchemaNode) {
90                 Preconditions.checkState(child instanceof SimpleNode<?>,
91                         "Data representation of LeafList should be SimpleNode - " + child.getNodeType());
92                 writeLeaf(writer, (SimpleNode<?>) child, (LeafSchemaNode) childSchema);
93             } else {
94                 throw new UnsupportedDataTypeException("Schema can be ContainerSchemaNode, ListSchemaNode, "
95                         + "LeafListSchemaNode, or LeafSchemaNode. Other types are not supported yet.");
96             }
97         }
98
99         for (Node<?> child : parent.getChildren()) {
100             SchemaLocation schemaLocation = findFirstSchemaForNode(child, parentSchema.getChildNodes(),
101                     new ArrayDeque<>(longestPathToElementViaChoiceCase));
102
103             DataSchemaNode childSchema = schemaLocation.getSchema();
104             if (childSchema instanceof LeafListSchemaNode) {
105                 foundLeafLists.remove((LeafListSchemaNode) childSchema);
106             } else if (childSchema instanceof ListSchemaNode) {
107                 foundLists.remove((ListSchemaNode) childSchema);
108             }
109         }
110     }
111
112     private List<String> resolveLongerPath(List<String> l1, List<String> l2) {
113         return l1.size() > l2.size() ? l1 : l2;
114     }
115
116     private SchemaLocation findFirstSchemaForNode(Node<?> node, Set<DataSchemaNode> dataSchemaNode,
117             Deque<String> pathIterator) {
118         Map<String, ChoiceNode> choiceSubnodes = new HashMap<>();
119         for (DataSchemaNode dsn : dataSchemaNode) {
120             if (dsn instanceof ChoiceNode) {
121                 choiceSubnodes.put(dsn.getQName().getLocalName(), (ChoiceNode) dsn);
122             } else if (node.getNodeType().getLocalName().equals(dsn.getQName().getLocalName())) {
123                 return new SchemaLocation(dsn);
124             }
125         }
126
127         for (ChoiceNode choiceSubnode : choiceSubnodes.values()) {
128             if ((!pathIterator.isEmpty() && pathIterator.peekLast().equals(choiceSubnode.getQName().getLocalName()))
129                     || pathIterator.isEmpty()) {
130                 String pathPartChoice = pathIterator.pollLast();
131                 for (ChoiceCaseNode concreteCase : choiceSubnode.getCases()) {
132                     if ((!pathIterator.isEmpty() && pathIterator.peekLast().equals(
133                             concreteCase.getQName().getLocalName()))
134                             || pathIterator.isEmpty()) {
135                         String pathPartCase = pathIterator.pollLast();
136                         SchemaLocation schemaLocation = findFirstSchemaForNode(node, concreteCase.getChildNodes(),
137                                 pathIterator);
138                         if (schemaLocation != null) {
139                             schemaLocation.addPathPart(concreteCase.getQName().getLocalName());
140                             schemaLocation.addPathPart(choiceSubnode.getQName().getLocalName());
141                             return schemaLocation;
142                         }
143                         if (pathPartCase != null) {
144                             pathIterator.addLast(pathPartCase);
145                         }
146                     }
147                 }
148                 if (pathPartChoice != null) {
149                     pathIterator.addLast(pathPartChoice);
150                 }
151             }
152         }
153         return null;
154     }
155
156     private void writeContainer(JsonWriter writer, CompositeNode node, ContainerSchemaNode schema) throws IOException {
157         writeName(node, schema, writer);
158         writer.beginObject();
159         writeChildrenOfParent(writer, node, schema);
160         writer.endObject();
161     }
162
163     private void writeList(JsonWriter writer, CompositeNode nodeParent, CompositeNode node, ListSchemaNode schema)
164             throws IOException {
165         writeName(node, schema, writer);
166         writer.beginArray();
167
168         if (nodeParent != null) {
169             List<CompositeNode> nodeLists = nodeParent.getCompositesByName(node.getNodeType());
170             for (CompositeNode nodeList : nodeLists) {
171                 writer.beginObject();
172                 writeChildrenOfParent(writer, nodeList, schema);
173                 writer.endObject();
174             }
175         } else {
176             writer.beginObject();
177             writeChildrenOfParent(writer, node, schema);
178             writer.endObject();
179         }
180
181         writer.endArray();
182     }
183
184     private void writeLeafList(JsonWriter writer, CompositeNode nodeParent, SimpleNode<?> node,
185             LeafListSchemaNode schema) throws IOException {
186         writeName(node, schema, writer);
187         writer.beginArray();
188
189         List<SimpleNode<?>> nodeLeafLists = nodeParent.getSimpleNodesByName(node.getNodeType());
190         for (SimpleNode<?> nodeLeafList : nodeLeafLists) {
191             writeValueOfNodeByType(writer, nodeLeafList, schema.getType());
192         }
193
194         writer.endArray();
195     }
196
197     private void writeLeaf(JsonWriter writer, SimpleNode<?> node, LeafSchemaNode schema) throws IOException {
198         writeName(node, schema, writer);
199         writeValueOfNodeByType(writer, node, schema.getType());
200     }
201
202     private void writeValueOfNodeByType(JsonWriter writer, SimpleNode<?> node, TypeDefinition<?> type)
203             throws IOException {
204
205         String value = String.valueOf(node.getValue());
206         // TODO check Leafref, InstanceIdentifierTypeDefinition,
207         // IdentityrefTypeDefinition, UnionTypeDefinition
208         TypeDefinition<?> baseType = resolveBaseTypeFrom(type);
209         if (baseType instanceof InstanceIdentifierTypeDefinition) {
210             writer.value(((InstanceIdentifierTypeDefinition) baseType).getPathStatement().toString());
211         } else if (baseType instanceof UnionTypeDefinition) {
212             processTypeIsUnionType(writer, (UnionTypeDefinition) baseType, value);
213         } else if (baseType instanceof DecimalTypeDefinition || baseType instanceof IntegerTypeDefinition
214                 || baseType instanceof UnsignedIntegerTypeDefinition) {
215             writer.value(new NumberForJsonWriter(value));
216         } else if (baseType instanceof BooleanTypeDefinition) {
217             writer.value(Boolean.parseBoolean(value));
218         } else if (baseType instanceof EmptyTypeDefinition) {
219             writeEmptyDataTypeToJson(writer);
220         } else {
221             writer.value(value.equals("null") ? "" : value);
222         }
223     }
224
225     private void processTypeIsUnionType(JsonWriter writer, UnionTypeDefinition unionType, String value)
226             throws IOException {
227         if (value == null) {
228             writeEmptyDataTypeToJson(writer);
229         } else if ((isNumber(value))
230                 && containsType(unionType, UnsignedIntegerTypeDefinition.class, IntegerTypeDefinition.class,
231                         DecimalTypeDefinition.class)) {
232             writer.value(new NumberForJsonWriter(value));
233         } else if (isBoolean(value) && containsType(unionType, BooleanTypeDefinition.class)) {
234             writer.value(Boolean.parseBoolean(value));
235         } else {
236             writer.value(value);
237         }
238     }
239
240     private boolean isBoolean(String value) {
241         if (value.equals("true") || value.equals("false")) {
242             return true;
243         }
244         return false;
245     }
246
247     private void writeEmptyDataTypeToJson(JsonWriter writer) throws IOException {
248         writer.beginArray();
249         writer.nullValue();
250         writer.endArray();
251     }
252
253     private boolean isNumber(String value) {
254         try {
255             Double.valueOf(value);
256         } catch (NumberFormatException e) {
257             return false;
258         }
259         return true;
260     }
261
262     private boolean containsType(UnionTypeDefinition unionType, Class<?>... searchedTypes) {
263         List<TypeDefinition<?>> allUnionSubtypes = resolveAllUnionSubtypesFrom(unionType);
264
265         for (TypeDefinition<?> unionSubtype : allUnionSubtypes) {
266             for (Class<?> searchedType : searchedTypes) {
267                 if (searchedType.isInstance(unionSubtype)) {
268                     return true;
269                 }
270             }
271         }
272         return false;
273     }
274
275     private List<TypeDefinition<?>> resolveAllUnionSubtypesFrom(UnionTypeDefinition inputType) {
276         List<TypeDefinition<?>> result = new ArrayList<>();
277         for (TypeDefinition<?> subtype : inputType.getTypes()) {
278             TypeDefinition<?> resolvedSubtype = subtype;
279
280             resolvedSubtype = resolveBaseTypeFrom(subtype);
281
282             if (resolvedSubtype instanceof UnionTypeDefinition) {
283                 List<TypeDefinition<?>> subtypesFromRecursion = resolveAllUnionSubtypesFrom((UnionTypeDefinition) resolvedSubtype);
284                 result.addAll(subtypesFromRecursion);
285             } else {
286                 result.add(resolvedSubtype);
287             }
288         }
289
290         return result;
291     }
292
293     private TypeDefinition<?> resolveBaseTypeFrom(TypeDefinition<?> type) {
294         return type.getBaseType() != null ? resolveBaseTypeFrom(type.getBaseType()) : type;
295     }
296
297     private void writeName(Node<?> node, DataSchemaNode schema, JsonWriter writer) throws IOException {
298         String nameForOutput = node.getNodeType().getLocalName();
299         if (schema.isAugmenting()) {
300             ControllerContext contContext = ControllerContext.getInstance();
301             CharSequence moduleName;
302             moduleName = contContext.toRestconfIdentifier(schema.getQName());
303             if (moduleName != null) {
304                 nameForOutput = moduleName.toString();
305             }
306         }
307         writer.name(nameForOutput);
308     }
309
310     private static final class NumberForJsonWriter extends Number {
311
312         private static final long serialVersionUID = -3147729419814417666L;
313         private final String value;
314
315         public NumberForJsonWriter(String value) {
316             this.value = value;
317         }
318
319         @Override
320         public int intValue() {
321             throw new IllegalStateException("Should not be invoked");
322         }
323
324         @Override
325         public long longValue() {
326             throw new IllegalStateException("Should not be invoked");
327         }
328
329         @Override
330         public float floatValue() {
331             throw new IllegalStateException("Should not be invoked");
332         }
333
334         @Override
335         public double doubleValue() {
336             throw new IllegalStateException("Should not be invoked");
337         }
338
339         @Override
340         public String toString() {
341             return value;
342         }
343
344     }
345
346 }