1 package org.opendaylight.controller.sal.rest.impl;
3 import static com.google.common.base.Preconditions.checkNotNull;
5 import java.io.IOException;
8 import javax.activation.UnsupportedDataTypeException;
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.*;
15 import com.google.common.base.Preconditions;
16 import com.google.gson.stream.JsonWriter;
20 private final Set<LeafListSchemaNode> foundLeafLists = new HashSet<>();
21 private final Set<ListSchemaNode> foundLists = new HashSet<>();
23 public void write(JsonWriter writer, CompositeNode data, DataNodeContainer schema) throws IOException {
24 Preconditions.checkNotNull(writer);
25 Preconditions.checkNotNull(data);
26 Preconditions.checkNotNull(schema);
30 if (schema instanceof ContainerSchemaNode) {
31 writeContainer(writer, data, (ContainerSchemaNode) schema);
32 } else if (schema instanceof ListSchemaNode) {
33 writeList(writer, null, data, (ListSchemaNode) schema);
35 throw new UnsupportedDataTypeException(
36 "Schema can be ContainerSchemaNode or ListSchemaNode. Other types are not supported yet.");
41 foundLeafLists.clear();
45 private void writeChildrenOfParent(JsonWriter writer, CompositeNode parent, DataNodeContainer parentSchema)
48 checkNotNull(parentSchema);
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(),
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());
61 throw new UnsupportedDataTypeException("Probably the data node \""
62 + child.getNodeType().getLocalName() + "\" is not conform to schema");
66 longestPathToElementViaChoiceCase = resolveLongerPath(longestPathToElementViaChoiceCase,
67 schemaLocation.getLocation());
69 DataSchemaNode childSchema = schemaLocation.getSchema();
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);
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);
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);
94 throw new UnsupportedDataTypeException("Schema can be ContainerSchemaNode, ListSchemaNode, "
95 + "LeafListSchemaNode, or LeafSchemaNode. Other types are not supported yet.");
99 for (Node<?> child : parent.getChildren()) {
100 SchemaLocation schemaLocation = findFirstSchemaForNode(child, parentSchema.getChildNodes(),
101 new ArrayDeque<>(longestPathToElementViaChoiceCase));
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);
112 private List<String> resolveLongerPath(List<String> l1, List<String> l2) {
113 return l1.size() > l2.size() ? l1 : l2;
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);
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(),
138 if (schemaLocation != null) {
139 schemaLocation.addPathPart(concreteCase.getQName().getLocalName());
140 schemaLocation.addPathPart(choiceSubnode.getQName().getLocalName());
141 return schemaLocation;
143 if (pathPartCase != null) {
144 pathIterator.addLast(pathPartCase);
148 if (pathPartChoice != null) {
149 pathIterator.addLast(pathPartChoice);
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);
163 private void writeList(JsonWriter writer, CompositeNode nodeParent, CompositeNode node, ListSchemaNode schema)
165 writeName(node, schema, writer);
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);
176 writer.beginObject();
177 writeChildrenOfParent(writer, node, schema);
184 private void writeLeafList(JsonWriter writer, CompositeNode nodeParent, SimpleNode<?> node,
185 LeafListSchemaNode schema) throws IOException {
186 writeName(node, schema, writer);
189 List<SimpleNode<?>> nodeLeafLists = nodeParent.getSimpleNodesByName(node.getNodeType());
190 for (SimpleNode<?> nodeLeafList : nodeLeafLists) {
191 writeValueOfNodeByType(writer, nodeLeafList, schema.getType());
197 private void writeLeaf(JsonWriter writer, SimpleNode<?> node, LeafSchemaNode schema) throws IOException {
198 writeName(node, schema, writer);
199 writeValueOfNodeByType(writer, node, schema.getType());
202 private void writeValueOfNodeByType(JsonWriter writer, SimpleNode<?> node, TypeDefinition<?> type)
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);
221 writer.value(value.equals("null") ? "" : value);
225 private void processTypeIsUnionType(JsonWriter writer, UnionTypeDefinition unionType, String value)
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));
240 private boolean isBoolean(String value) {
241 if (value.equals("true") || value.equals("false")) {
247 private void writeEmptyDataTypeToJson(JsonWriter writer) throws IOException {
253 private boolean isNumber(String value) {
255 Double.valueOf(value);
256 } catch (NumberFormatException e) {
262 private boolean containsType(UnionTypeDefinition unionType, Class<?>... searchedTypes) {
263 List<TypeDefinition<?>> allUnionSubtypes = resolveAllUnionSubtypesFrom(unionType);
265 for (TypeDefinition<?> unionSubtype : allUnionSubtypes) {
266 for (Class<?> searchedType : searchedTypes) {
267 if (searchedType.isInstance(unionSubtype)) {
275 private List<TypeDefinition<?>> resolveAllUnionSubtypesFrom(UnionTypeDefinition inputType) {
276 List<TypeDefinition<?>> result = new ArrayList<>();
277 for (TypeDefinition<?> subtype : inputType.getTypes()) {
278 TypeDefinition<?> resolvedSubtype = subtype;
280 resolvedSubtype = resolveBaseTypeFrom(subtype);
282 if (resolvedSubtype instanceof UnionTypeDefinition) {
283 List<TypeDefinition<?>> subtypesFromRecursion = resolveAllUnionSubtypesFrom((UnionTypeDefinition) resolvedSubtype);
284 result.addAll(subtypesFromRecursion);
286 result.add(resolvedSubtype);
293 private TypeDefinition<?> resolveBaseTypeFrom(TypeDefinition<?> type) {
294 return type.getBaseType() != null ? resolveBaseTypeFrom(type.getBaseType()) : type;
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();
307 writer.name(nameForOutput);
310 private static final class NumberForJsonWriter extends Number {
312 private static final long serialVersionUID = -3147729419814417666L;
313 private final String value;
315 public NumberForJsonWriter(String value) {
320 public int intValue() {
321 throw new IllegalStateException("Should not be invoked");
325 public long longValue() {
326 throw new IllegalStateException("Should not be invoked");
330 public float floatValue() {
331 throw new IllegalStateException("Should not be invoked");
335 public double doubleValue() {
336 throw new IllegalStateException("Should not be invoked");
340 public String toString() {