2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.codec.gson;
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.gson.JsonIOException;
14 import com.google.gson.JsonParseException;
15 import com.google.gson.JsonSyntaxException;
16 import com.google.gson.stream.JsonReader;
17 import com.google.gson.stream.JsonToken;
18 import com.google.gson.stream.MalformedJsonException;
20 import java.io.Closeable;
21 import java.io.EOFException;
22 import java.io.Flushable;
23 import java.io.IOException;
25 import java.security.InvalidParameterException;
26 import java.util.ArrayDeque;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Deque;
30 import java.util.HashSet;
31 import java.util.List;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
36 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO;
37 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestCodecFactory;
38 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestUtil;
39 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestUtil.PrefixMapingFromJson;
40 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.SchemaContextUtils;
41 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
43 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
44 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
45 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
49 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
54 * This class parses JSON elements from a GSON JsonReader. It disallows multiple elements of the same name unlike the
55 * default GSON JsonParser.
58 public final class JsonParserStream implements Closeable, Flushable {
59 private final Deque<URI> namespaces = new ArrayDeque<>();
60 private final NormalizedNodeStreamWriter writer;
61 private final SchemaContextUtils utils;
62 private final RestCodecFactory codecs;
63 private final SchemaContext schema;
65 private JsonParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
66 this.schema = Preconditions.checkNotNull(schemaContext);
67 this.utils = SchemaContextUtils.create(schemaContext);
68 this.writer = Preconditions.checkNotNull(writer);
69 this.codecs = RestCodecFactory.create(utils);
72 public static JsonParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
73 return new JsonParserStream(writer, schemaContext);
76 public JsonParserStream parse(final JsonReader reader) throws JsonIOException, JsonSyntaxException {
77 // code copied from gson's JsonParser and Stream classes
79 boolean lenient = reader.isLenient();
80 reader.setLenient(true);
81 boolean isEmpty = true;
85 CompositeNodeDataWithSchema compositeNodeDataWithSchema = new CompositeNodeDataWithSchema(schema);
86 read(reader, compositeNodeDataWithSchema);
87 compositeNodeDataWithSchema.writeToStream(writer);
90 // return read(reader);
91 } catch (EOFException e) {
94 // return JsonNull.INSTANCE;
96 // The stream ended prematurely so it is likely a syntax error.
97 throw new JsonSyntaxException(e);
98 } catch (MalformedJsonException e) {
99 throw new JsonSyntaxException(e);
100 } catch (IOException e) {
101 throw new JsonIOException(e);
102 } catch (NumberFormatException e) {
103 throw new JsonSyntaxException(e);
104 } catch (StackOverflowError | OutOfMemoryError e) {
105 throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
107 reader.setLenient(lenient);
111 public void read(final JsonReader in, final AbstractNodeDataWithSchema parent) throws IOException {
113 final JsonToken peek = in.peek();
114 Optional<String> value = Optional.absent();
118 value = Optional.of(in.nextString());
121 value = Optional.of(Boolean.toString(in.nextBoolean()));
125 value = Optional.of((String) null);
130 if (value.isPresent()) {
131 final Object translatedValue = translateValueByType(value.get(), parent.getSchema());
132 ((SimpleNodeDataWithSchema) parent).setValue(translatedValue);
138 while (in.hasNext()) {
139 AbstractNodeDataWithSchema newChild = null;
140 if (parent instanceof ListNodeDataWithSchema) {
141 newChild = new ListEntryNodeDataWithSchema(parent.getSchema());
142 ((CompositeNodeDataWithSchema) parent).addChild(newChild);
143 } else if (parent instanceof LeafListNodeDataWithSchema) {
144 newChild = new LeafListEntryNodeDataWithSchema(parent.getSchema());
145 ((CompositeNodeDataWithSchema) parent).addChild(newChild);
152 Set<String> namesakes = new HashSet<>();
154 while (in.hasNext()) {
155 final String jsonElementName = in.nextName();
156 final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName);
157 final String localName = namespaceAndName.getName();
158 addNamespace(namespaceAndName.getUri());
159 if (namesakes.contains(jsonElementName)) {
160 throw new JsonSyntaxException("Duplicate name " + jsonElementName + " in JSON input.");
162 namesakes.add(jsonElementName);
163 final Deque<DataSchemaNode> childDataSchemaNodes = findSchemaNodeByNameAndNamespace(parent.getSchema(),
164 localName, getCurrentNamespace());
165 if (childDataSchemaNodes.isEmpty()) {
166 throw new IllegalStateException("Schema for node with name " + localName + " and namespace "
167 + getCurrentNamespace() + " doesn't exist.");
170 AbstractNodeDataWithSchema newChild;
171 newChild = ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes);
172 // FIXME:anyxml data shouldn't be skipped but should be loaded somehow. will be specified after 17AUG2014
173 if (newChild instanceof AnyXmlNodeDataWithSchema) {
189 private Object translateValueByType(final String value, final DataSchemaNode node) {
190 final TypeDefinition<? extends Object> typeDefinition = typeDefinition(node);
191 if (typeDefinition == null) {
195 final Object inputValue;
196 if (typeDefinition instanceof IdentityrefTypeDefinition) {
197 inputValue = valueAsIdentityRef(value);
198 } else if (typeDefinition instanceof InstanceIdentifierTypeDefinition) {
199 inputValue = valueAsInstanceIdentifier(value);
204 return codecs.codecFor(typeDefinition).deserialize(inputValue);
207 private static TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
208 TypeDefinition<?> baseType = null;
209 if (node instanceof LeafListSchemaNode) {
210 baseType = ((LeafListSchemaNode) node).getType();
211 } else if (node instanceof LeafSchemaNode) {
212 baseType = ((LeafSchemaNode) node).getType();
213 } else if (node instanceof AnyXmlSchemaNode) {
216 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());
219 if (baseType != null) {
220 while (baseType.getBaseType() != null) {
221 baseType = baseType.getBaseType();
227 private static Object valueAsInstanceIdentifier(final String value) {
228 // it could be instance-identifier Built-In Type
229 if (!value.isEmpty() && value.charAt(0) == '/') {
230 IdentityValuesDTO resolvedValue = RestUtil.asInstanceIdentifier(value, new PrefixMapingFromJson());
231 if (resolvedValue != null) {
232 return resolvedValue;
235 throw new InvalidParameterException("Value for instance-identifier doesn't have correct format");
238 private static IdentityValuesDTO valueAsIdentityRef(final String value) {
239 // it could be identityref Built-In Type
240 URI namespace = getNamespaceFor(value);
241 if (namespace != null) {
242 return new IdentityValuesDTO(namespace.toString(), getLocalNameFor(value), null, value);
244 throw new InvalidParameterException("Value for identityref has to be in format moduleName:localName.");
247 private static URI getNamespaceFor(final String jsonElementName) {
248 // The string needs to me in form "moduleName:localName"
249 final int idx = jsonElementName.indexOf(':');
250 if (idx == -1 || jsonElementName.indexOf(':', idx + 1) != -1) {
254 // FIXME: is this correct? This should be looking up module name instead
255 return URI.create(jsonElementName.substring(0, idx));
258 private static String getLocalNameFor(final String jsonElementName) {
259 // The string needs to me in form "moduleName:localName"
260 final int idx = jsonElementName.indexOf(':');
261 if (idx == -1 || jsonElementName.indexOf(':', idx + 1) != -1) {
262 return jsonElementName;
265 return jsonElementName.substring(idx + 1);
268 private void removeNamespace() {
272 private void addNamespace(final Optional<URI> namespace) {
273 if (!namespace.isPresent()) {
274 if (namespaces.isEmpty()) {
275 throw new IllegalStateException("Namespace has to be specified at top level.");
277 namespaces.push(namespaces.peek());
280 namespaces.push(namespace.get());
284 private NamespaceAndName resolveNamespace(final String childName) {
285 int lastIndexOfColon = childName.lastIndexOf(':');
286 String moduleNamePart = null;
287 String nodeNamePart = null;
288 URI namespace = null;
289 if (lastIndexOfColon != -1) {
290 moduleNamePart = childName.substring(0, lastIndexOfColon);
291 nodeNamePart = childName.substring(lastIndexOfColon + 1);
292 namespace = utils.findNamespaceByModuleName(moduleNamePart);
294 nodeNamePart = childName;
297 Optional<URI> namespaceOpt = namespace == null ? Optional.<URI> absent() : Optional.of(namespace);
298 return new NamespaceAndName(nodeNamePart, namespaceOpt);
301 private URI getCurrentNamespace() {
302 return namespaces.peek();
306 * Returns stack of schema nodes via which it was necessary to pass to get schema node with specified
307 * {@code childName} and {@code namespace}
309 * @param dataSchemaNode
312 * @return stack of schema nodes via which it was passed through. If found schema node is direct child then stack
313 * contains only one node. If it is found under choice and case then stack should contains 2*n+1 element
314 * (where n is number of choices through it was passed)
316 private Deque<DataSchemaNode> findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode,
317 final String childName, final URI namespace) {
318 final Deque<DataSchemaNode> result = new ArrayDeque<>();
319 List<ChoiceNode> childChoices = new ArrayList<>();
320 if (dataSchemaNode instanceof DataNodeContainer) {
321 for (DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
322 if (childNode instanceof ChoiceNode) {
323 childChoices.add((ChoiceNode) childNode);
325 final QName childQName = childNode.getQName();
326 if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) {
327 result.push(childNode);
333 // try to find data schema node in choice (looking for first match)
334 for (ChoiceNode choiceNode : childChoices) {
335 for (ChoiceCaseNode concreteCase : choiceNode.getCases()) {
336 Deque<DataSchemaNode> resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName,
338 if (!resultFromRecursion.isEmpty()) {
339 resultFromRecursion.push(concreteCase);
340 resultFromRecursion.push(choiceNode);
341 return resultFromRecursion;
348 private static class NamespaceAndName {
349 private final Optional<URI> uri;
350 private final String name;
352 public NamespaceAndName(final String name, final Optional<URI> uri) {
357 public String getName() {
361 public Optional<URI> getUri() {
367 public void flush() throws IOException {
372 public void close() throws IOException {