Merge "Bug 1029: Remove dead code: sal-schema-repository-api"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / rest / impl / JsonMapper.java
1 /*
2  * Copyright (c) 2014 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.controller.sal.rest.impl;
9
10 import static com.google.common.base.Preconditions.checkNotNull;
11
12 import java.io.IOException;
13 import java.net.URI;
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Set;
18
19 import javax.activation.UnsupportedDataTypeException;
20
21 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
22 import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
23 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
24 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO.IdentityValue;
25 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO.Predicate;
26 import org.opendaylight.controller.sal.restconf.impl.RestCodec;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
29 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.Node;
31 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
33 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
41 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
42 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import com.google.common.base.Preconditions;
52 import com.google.gson.stream.JsonWriter;
53
54 class JsonMapper {
55
56     private final Set<LeafListSchemaNode> foundLeafLists = new HashSet<>();
57     private final Set<ListSchemaNode> foundLists = new HashSet<>();
58     private MountInstance mountPoint;
59     private final Logger logger = LoggerFactory.getLogger(JsonMapper.class);
60
61     public void write(final JsonWriter writer, final CompositeNode data, final DataNodeContainer schema, final MountInstance mountPoint)
62             throws IOException {
63         Preconditions.checkNotNull(writer);
64         Preconditions.checkNotNull(data);
65         Preconditions.checkNotNull(schema);
66         this.mountPoint = mountPoint;
67
68         writer.beginObject();
69
70         if (schema instanceof ContainerSchemaNode) {
71             writeContainer(writer, data, (ContainerSchemaNode) schema);
72         } else if (schema instanceof ListSchemaNode) {
73             writeList(writer, null, data, (ListSchemaNode) schema);
74         } else {
75             throw new UnsupportedDataTypeException(
76                     "Schema can be ContainerSchemaNode or ListSchemaNode. Other types are not supported yet.");
77         }
78
79         writer.endObject();
80
81         foundLeafLists.clear();
82         foundLists.clear();
83     }
84
85     private void writeChildrenOfParent(final JsonWriter writer, final CompositeNode parent, final DataNodeContainer parentSchema)
86             throws IOException {
87         checkNotNull(parent);
88
89         Set<DataSchemaNode> parentSchemaChildNodes = parentSchema == null ?
90                                    Collections.<DataSchemaNode>emptySet() : parentSchema.getChildNodes();
91
92
93         for (Node<?> child : parent.getValue()) {
94             DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchemaChildNodes);
95
96             if (childSchema == null) {
97                 // Node may not conform to schema or allows "anyxml" - we'll process it.
98
99                 logger.debug( "No schema found for data node \"" + child.getNodeType() );
100
101                 handleNoSchemaFound( writer, child, parent );
102             }
103             else if (childSchema instanceof ContainerSchemaNode) {
104                 Preconditions.checkState(child instanceof CompositeNode,
105                         "Data representation of Container should be CompositeNode - " + child.getNodeType());
106                 writeContainer(writer, (CompositeNode) child, (ContainerSchemaNode) childSchema);
107             } else if (childSchema instanceof ListSchemaNode) {
108                 if (!foundLists.contains(childSchema)) {
109                     Preconditions.checkState(child instanceof CompositeNode,
110                             "Data representation of List should be CompositeNode - " + child.getNodeType());
111                     foundLists.add((ListSchemaNode) childSchema);
112                     writeList(writer, parent, (CompositeNode) child, (ListSchemaNode) childSchema);
113                 }
114             } else if (childSchema instanceof LeafListSchemaNode) {
115                 if (!foundLeafLists.contains(childSchema)) {
116                     Preconditions.checkState(child instanceof SimpleNode<?>,
117                             "Data representation of LeafList should be SimpleNode - " + child.getNodeType());
118                     foundLeafLists.add((LeafListSchemaNode) childSchema);
119                     writeLeafList(writer, parent, (SimpleNode<?>) child, (LeafListSchemaNode) childSchema);
120                 }
121             } else if (childSchema instanceof LeafSchemaNode) {
122                 Preconditions.checkState(child instanceof SimpleNode<?>,
123                         "Data representation of LeafList should be SimpleNode - " + child.getNodeType());
124                 writeLeaf(writer, (SimpleNode<?>) child, (LeafSchemaNode) childSchema);
125             } else {
126                 throw new UnsupportedDataTypeException("Schema can be ContainerSchemaNode, ListSchemaNode, "
127                         + "LeafListSchemaNode, or LeafSchemaNode. Other types are not supported yet.");
128             }
129         }
130
131         for (Node<?> child : parent.getValue()) {
132             DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchemaChildNodes);
133             if (childSchema instanceof LeafListSchemaNode) {
134                 foundLeafLists.remove(childSchema);
135             } else if (childSchema instanceof ListSchemaNode) {
136                 foundLists.remove(childSchema);
137             }
138         }
139     }
140
141     private void handleNoSchemaFound( final JsonWriter writer, final Node<?> node,
142                                       final CompositeNode parent ) throws IOException {
143         if( node instanceof SimpleNode<?> ) {
144             writeName( node, null, writer );
145             Object value = node.getValue();
146             if( value != null ) {
147                 writer.value( String.valueOf( value ) );
148             }
149         } else { // CompositeNode
150             Preconditions.checkState( node instanceof CompositeNode,
151                     "Data representation of Container should be CompositeNode - " + node.getNodeType() );
152
153             writeContainer( writer, (CompositeNode) node, null );
154         }
155     }
156
157     private DataSchemaNode findFirstSchemaForNode(final Node<?> node, final Set<DataSchemaNode> dataSchemaNode) {
158         for (DataSchemaNode dsn : dataSchemaNode) {
159             if (node.getNodeType().equals(dsn.getQName())) {
160                 return dsn;
161             } else if (dsn instanceof ChoiceNode) {
162                 for (ChoiceCaseNode choiceCase : ((ChoiceNode) dsn).getCases()) {
163                     DataSchemaNode foundDsn = findFirstSchemaForNode(node, choiceCase.getChildNodes());
164                     if (foundDsn != null) {
165                         return foundDsn;
166                     }
167                 }
168             }
169         }
170         return null;
171     }
172
173     private void writeContainer(final JsonWriter writer, final CompositeNode node, final ContainerSchemaNode schema) throws IOException {
174         writeName(node, schema, writer);
175         writer.beginObject();
176         writeChildrenOfParent(writer, node, schema);
177         writer.endObject();
178     }
179
180     private void writeList(final JsonWriter writer, final CompositeNode nodeParent, final CompositeNode node, final ListSchemaNode schema)
181             throws IOException {
182         writeName(node, schema, writer);
183         writer.beginArray();
184
185         if (nodeParent != null) {
186             List<CompositeNode> nodeLists = nodeParent.getCompositesByName(node.getNodeType());
187             for (CompositeNode nodeList : nodeLists) {
188                 writer.beginObject();
189                 writeChildrenOfParent(writer, nodeList, schema);
190                 writer.endObject();
191             }
192         } else {
193             writer.beginObject();
194             writeChildrenOfParent(writer, node, schema);
195             writer.endObject();
196         }
197
198         writer.endArray();
199     }
200
201     private void writeLeafList(final JsonWriter writer, final CompositeNode nodeParent, final SimpleNode<?> node,
202             final LeafListSchemaNode schema) throws IOException {
203         writeName(node, schema, writer);
204         writer.beginArray();
205
206         List<SimpleNode<?>> nodeLeafLists = nodeParent.getSimpleNodesByName(node.getNodeType());
207         for (SimpleNode<?> nodeLeafList : nodeLeafLists) {
208             writeValueOfNodeByType(writer, nodeLeafList, schema.getType(), schema);
209         }
210         writer.endArray();
211     }
212
213     private void writeLeaf(final JsonWriter writer, final SimpleNode<?> node, final LeafSchemaNode schema) throws IOException {
214         writeName(node, schema, writer);
215         writeValueOfNodeByType(writer, node, schema.getType(), schema);
216     }
217
218     private void writeValueOfNodeByType(final JsonWriter writer, final SimpleNode<?> node, final TypeDefinition<?> type,
219             final DataSchemaNode schema) throws IOException {
220
221         TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(type);
222
223         if (node.getValue() == null && !(baseType instanceof EmptyTypeDefinition)) {
224             logger.debug("While generationg JSON output null value was found for type "
225                     + baseType.getClass().getSimpleName() + ".");
226         }
227
228         if (baseType instanceof IdentityrefTypeDefinition) {
229             if (node.getValue() instanceof QName) {
230                 IdentityValuesDTO valueDTO = (IdentityValuesDTO) RestCodec.from(baseType, mountPoint).serialize(
231                         node.getValue());
232                 IdentityValue valueFromDTO = valueDTO.getValuesWithNamespaces().get(0);
233                 String moduleName;
234                 if (mountPoint != null) {
235                     moduleName = ControllerContext.getInstance().findModuleNameByNamespace(mountPoint,
236                             URI.create(valueFromDTO.getNamespace()));
237                 } else {
238                     moduleName = ControllerContext.getInstance().findModuleNameByNamespace(
239                             URI.create(valueFromDTO.getNamespace()));
240                 }
241                 writer.value(moduleName + ":" + valueFromDTO.getValue());
242             } else {
243                 writeStringRepresentation(writer, node, baseType, QName.class);
244             }
245         } else if (baseType instanceof InstanceIdentifierTypeDefinition) {
246             if (node.getValue() instanceof InstanceIdentifier) {
247                 IdentityValuesDTO valueDTO = (IdentityValuesDTO) RestCodec.from(baseType, mountPoint).serialize(
248                         node.getValue());
249                 writeIdentityValuesDTOToJson(writer, valueDTO);
250             } else {
251                 writeStringRepresentation(writer, node, baseType, InstanceIdentifier.class);
252             }
253         } else if (baseType instanceof DecimalTypeDefinition || baseType instanceof IntegerTypeDefinition
254                 || baseType instanceof UnsignedIntegerTypeDefinition) {
255             writer.value(new NumberForJsonWriter((String) RestCodec.from(baseType, mountPoint).serialize(
256                     node.getValue())));
257         } else if (baseType instanceof BooleanTypeDefinition) {
258             writer.value(Boolean.parseBoolean((String) RestCodec.from(baseType, mountPoint).serialize(node.getValue())));
259         } else if (baseType instanceof EmptyTypeDefinition) {
260             writeEmptyDataTypeToJson(writer);
261         } else {
262             String value = String.valueOf(RestCodec.from(baseType, mountPoint).serialize(node.getValue()));
263             if (value == null) {
264                 value = String.valueOf(node.getValue());
265             }
266             writer.value(value.equals("null") ? "" : value);
267         }
268     }
269
270     private void writeIdentityValuesDTOToJson(final JsonWriter writer, final IdentityValuesDTO valueDTO) throws IOException {
271         StringBuilder result = new StringBuilder();
272         for (IdentityValue identityValue : valueDTO.getValuesWithNamespaces()) {
273             result.append("/");
274
275             writeModuleNameAndIdentifier(result, identityValue);
276             if (identityValue.getPredicates() != null && !identityValue.getPredicates().isEmpty()) {
277                 for (Predicate predicate : identityValue.getPredicates()) {
278                     IdentityValue identityValuePredicate = predicate.getName();
279                     result.append("[");
280                     if (identityValuePredicate == null) {
281                         result.append(".");
282                     } else {
283                         writeModuleNameAndIdentifier(result, identityValuePredicate);
284                     }
285                     result.append("='");
286                     result.append(predicate.getValue());
287                     result.append("'");
288                     result.append("]");
289                 }
290             }
291         }
292
293         writer.value(result.toString());
294     }
295
296     private void writeModuleNameAndIdentifier(final StringBuilder result, final IdentityValue identityValue) {
297         String moduleName = ControllerContext.getInstance().findModuleNameByNamespace(
298                 URI.create(identityValue.getNamespace()));
299         if (moduleName != null && !moduleName.isEmpty()) {
300             result.append(moduleName);
301             result.append(":");
302         }
303         result.append(identityValue.getValue());
304     }
305
306     private void writeStringRepresentation(final JsonWriter writer, final SimpleNode<?> node, final TypeDefinition<?> baseType,
307             final Class<?> requiredType) throws IOException {
308         Object value = node.getValue();
309         logger.debug("Value of " + baseType.getQName().getNamespace() + ":" + baseType.getQName().getLocalName()
310                 + " is not instance of " + requiredType.getClass() + " but is " + node.getValue().getClass());
311         if (value == null) {
312             writer.value("");
313         } else {
314             writer.value(String.valueOf(value));
315         }
316     }
317
318     private void writeEmptyDataTypeToJson(final JsonWriter writer) throws IOException {
319         writer.beginArray();
320         writer.nullValue();
321         writer.endArray();
322     }
323
324     private void writeName(final Node<?> node, final DataSchemaNode schema, final JsonWriter writer) throws IOException {
325         String nameForOutput = node.getNodeType().getLocalName();
326         if ( schema != null && schema.isAugmenting()) {
327             ControllerContext contContext = ControllerContext.getInstance();
328             CharSequence moduleName = null;
329             if (mountPoint == null) {
330                 moduleName = contContext.toRestconfIdentifier(schema.getQName());
331             } else {
332                 moduleName = contContext.toRestconfIdentifier(mountPoint, schema.getQName());
333             }
334             if (moduleName != null) {
335                 nameForOutput = moduleName.toString();
336             } else {
337                 logger.info("Module '{}' was not found in schema from mount point", schema.getQName());
338             }
339         }
340         writer.name(nameForOutput);
341     }
342
343     private static final class NumberForJsonWriter extends Number {
344
345         private static final long serialVersionUID = -3147729419814417666L;
346         private final String value;
347
348         public NumberForJsonWriter(final String value) {
349             this.value = value;
350         }
351
352         @Override
353         public int intValue() {
354             throw new IllegalStateException("Should not be invoked");
355         }
356
357         @Override
358         public long longValue() {
359             throw new IllegalStateException("Should not be invoked");
360         }
361
362         @Override
363         public float floatValue() {
364             throw new IllegalStateException("Should not be invoked");
365         }
366
367         @Override
368         public double doubleValue() {
369             throw new IllegalStateException("Should not be invoked");
370         }
371
372         @Override
373         public String toString() {
374             return value;
375         }
376
377     }
378
379 }