Merge "Moved Schema Aware logic from DataStore to SchemaAwareDataStore adapter"
[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         for (Node<?> child : parent.getChildren()) {
51             DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchema.getChildNodes());
52             if (childSchema == null) {
53                 throw new UnsupportedDataTypeException("Probably the data node \"" + child.getNodeType().getLocalName()
54                         + "\" is not conform to schema");
55             }
56
57             if (childSchema instanceof ContainerSchemaNode) {
58                 Preconditions.checkState(child instanceof CompositeNode,
59                         "Data representation of Container should be CompositeNode - " + child.getNodeType());
60                 writeContainer(writer, (CompositeNode) child, (ContainerSchemaNode) childSchema);
61             } else if (childSchema instanceof ListSchemaNode) {
62                 if (!foundLists.contains(childSchema)) {
63                     Preconditions.checkState(child instanceof CompositeNode,
64                             "Data representation of List should be CompositeNode - " + child.getNodeType());
65                     foundLists.add((ListSchemaNode) childSchema);
66                     writeList(writer, parent, (CompositeNode) child, (ListSchemaNode) childSchema);
67                 }
68             } else if (childSchema instanceof LeafListSchemaNode) {
69                 if (!foundLeafLists.contains(childSchema)) {
70                     Preconditions.checkState(child instanceof SimpleNode<?>,
71                             "Data representation of LeafList should be SimpleNode - " + child.getNodeType());
72                     foundLeafLists.add((LeafListSchemaNode) childSchema);
73                     writeLeafList(writer, parent, (SimpleNode<?>) child, (LeafListSchemaNode) childSchema);
74                 }
75             } else if (childSchema instanceof LeafSchemaNode) {
76                 Preconditions.checkState(child instanceof SimpleNode<?>,
77                         "Data representation of LeafList should be SimpleNode - " + child.getNodeType());
78                 writeLeaf(writer, (SimpleNode<?>) child, (LeafSchemaNode) childSchema);
79             } else {
80                 throw new UnsupportedDataTypeException("Schema can be ContainerSchemaNode, ListSchemaNode, "
81                         + "LeafListSchemaNode, or LeafSchemaNode. Other types are not supported yet.");
82             }
83         }
84
85         for (Node<?> child : parent.getChildren()) {
86             DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchema.getChildNodes());
87             if (childSchema instanceof LeafListSchemaNode) {
88                 foundLeafLists.remove((LeafListSchemaNode) childSchema);
89             } else if (childSchema instanceof ListSchemaNode) {
90                 foundLists.remove((ListSchemaNode) childSchema);
91             }
92         }
93     }
94
95     private DataSchemaNode findFirstSchemaForNode(Node<?> node, Set<DataSchemaNode> dataSchemaNode) {
96         for (DataSchemaNode dsn : dataSchemaNode) {
97             if (node.getNodeType().getLocalName().equals(dsn.getQName().getLocalName())) {
98                 return dsn;
99             }
100         }
101         return null;
102     }
103
104     private void writeContainer(JsonWriter writer, CompositeNode node, ContainerSchemaNode schema) throws IOException {
105         writeName(node, schema, writer);
106         writer.beginObject();
107         writeChildrenOfParent(writer, node, schema);
108         writer.endObject();
109     }
110
111     private void writeList(JsonWriter writer, CompositeNode nodeParent, CompositeNode node, ListSchemaNode schema)
112             throws IOException {
113         writeName(node, schema, writer);
114         writer.beginArray();
115
116         if (nodeParent != null) {
117             List<CompositeNode> nodeLists = nodeParent.getCompositesByName(node.getNodeType());
118             for (CompositeNode nodeList : nodeLists) {
119                 writer.beginObject();
120                 writeChildrenOfParent(writer, nodeList, schema);
121                 writer.endObject();
122             }
123         } else {
124             writer.beginObject();
125             writeChildrenOfParent(writer, node, schema);
126             writer.endObject();
127         }
128
129         writer.endArray();
130     }
131
132     private void writeLeafList(JsonWriter writer, CompositeNode nodeParent, SimpleNode<?> node,
133             LeafListSchemaNode schema) throws IOException {
134         writeName(node, schema, writer);
135         writer.beginArray();
136
137         List<SimpleNode<?>> nodeLeafLists = nodeParent.getSimpleNodesByName(node.getNodeType());
138         for (SimpleNode<?> nodeLeafList : nodeLeafLists) {
139             writeValueOfNodeByType(writer, nodeLeafList, schema.getType());
140         }
141
142         writer.endArray();
143     }
144
145     private void writeLeaf(JsonWriter writer, SimpleNode<?> node, LeafSchemaNode schema) throws IOException {
146         writeName(node, schema, writer);
147         writeValueOfNodeByType(writer, node, schema.getType());
148     }
149
150     private void writeValueOfNodeByType(JsonWriter writer, SimpleNode<?> node, TypeDefinition<?> type)
151             throws IOException {
152
153         String value = String.valueOf(node.getValue());
154         // TODO check Leafref, InstanceIdentifierTypeDefinition,
155         // IdentityrefTypeDefinition, UnionTypeDefinition
156         TypeDefinition<?> baseType = resolveBaseTypeFrom(type);
157         if (baseType instanceof InstanceIdentifierTypeDefinition) {
158             writer.value(((InstanceIdentifierTypeDefinition) baseType).getPathStatement().toString());
159         } else if (baseType instanceof UnionTypeDefinition) {
160             processTypeIsUnionType(writer, (UnionTypeDefinition) baseType, value);
161         } else if (baseType instanceof DecimalTypeDefinition || baseType instanceof IntegerTypeDefinition
162                 || baseType instanceof UnsignedIntegerTypeDefinition) {
163             writer.value(new NumberForJsonWriter(value));
164         } else if (baseType instanceof BooleanTypeDefinition) {
165             writer.value(Boolean.parseBoolean(value));
166         } else if (baseType instanceof EmptyTypeDefinition) {
167             writeEmptyDataTypeToJson(writer);
168         } else {
169             writer.value(value.equals("null") ? "" : value);
170         }
171     }
172
173     private void processTypeIsUnionType(JsonWriter writer, UnionTypeDefinition unionType, String value)
174             throws IOException {
175         if (value == null) {
176             writeEmptyDataTypeToJson(writer);
177         } else if ((isNumber(value))
178                 && containsType(unionType, UnsignedIntegerTypeDefinition.class, IntegerTypeDefinition.class,
179                         DecimalTypeDefinition.class)) {
180             writer.value(new NumberForJsonWriter(value));
181         } else if (isBoolean(value) && containsType(unionType, BooleanTypeDefinition.class)) {
182             writer.value(Boolean.parseBoolean(value));
183         } else {
184             writer.value(value);
185         }
186     }
187
188     private boolean isBoolean(String value) {
189         if (value.equals("true") || value.equals("false")) {
190             return true;
191         }
192         return false;
193     }
194
195     private void writeEmptyDataTypeToJson(JsonWriter writer) throws IOException {
196         writer.beginArray();
197         writer.nullValue();
198         writer.endArray();
199     }
200
201     private boolean isNumber(String value) {
202         try {
203             Double.valueOf(value);
204         } catch (NumberFormatException e) {
205             return false;
206         }
207         return true;
208     }
209
210     private boolean containsType(UnionTypeDefinition unionType, Class<?>... searchedTypes) {
211         List<TypeDefinition<?>> allUnionSubtypes = resolveAllUnionSubtypesFrom(unionType);
212
213         for (TypeDefinition<?> unionSubtype : allUnionSubtypes) {
214             for (Class<?> searchedType : searchedTypes) {
215                 if (searchedType.isInstance(unionSubtype)) {
216                     return true;
217                 }
218             }
219         }
220         return false;
221     }
222
223     private List<TypeDefinition<?>> resolveAllUnionSubtypesFrom(UnionTypeDefinition inputType) {
224         List<TypeDefinition<?>> result = new ArrayList<>();
225         for (TypeDefinition<?> subtype : inputType.getTypes()) {
226             TypeDefinition<?> resolvedSubtype = subtype;
227
228             resolvedSubtype = resolveBaseTypeFrom(subtype);
229
230             if (resolvedSubtype instanceof UnionTypeDefinition) {
231                 List<TypeDefinition<?>> subtypesFromRecursion = resolveAllUnionSubtypesFrom((UnionTypeDefinition) resolvedSubtype);
232                 result.addAll(subtypesFromRecursion);
233             } else {
234                 result.add(resolvedSubtype);
235             }
236         }
237
238         return result;
239     }
240
241     private TypeDefinition<?> resolveBaseTypeFrom(TypeDefinition<?> type) {
242         return type.getBaseType() != null ? resolveBaseTypeFrom(type.getBaseType()) : type;
243     }
244
245     private void writeName(Node<?> node, DataSchemaNode schema, JsonWriter writer) throws IOException {
246         String nameForOutput = node.getNodeType().getLocalName();
247         if (schema.isAugmenting()) {
248             ControllerContext contContext = ControllerContext.getInstance();
249             CharSequence moduleName;
250             moduleName = contContext.toRestconfIdentifier(schema.getQName());
251             if (moduleName != null) {
252                 nameForOutput = moduleName.toString();
253             }
254         }
255         writer.name(nameForOutput);
256     }
257
258     private static final class NumberForJsonWriter extends Number {
259
260         private static final long serialVersionUID = -3147729419814417666L;
261         private final String value;
262
263         public NumberForJsonWriter(String value) {
264             this.value = value;
265         }
266
267         @Override
268         public int intValue() {
269             throw new IllegalStateException("Should not be invoked");
270         }
271
272         @Override
273         public long longValue() {
274             throw new IllegalStateException("Should not be invoked");
275         }
276
277         @Override
278         public float floatValue() {
279             throw new IllegalStateException("Should not be invoked");
280         }
281
282         @Override
283         public double doubleValue() {
284             throw new IllegalStateException("Should not be invoked");
285         }
286
287         @Override
288         public String toString() {
289             return value;
290         }
291
292     }
293
294 }