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.common.base.Splitter;
14 import com.google.common.collect.Iterators;
15 import com.google.gson.JsonIOException;
16 import com.google.gson.JsonParseException;
17 import com.google.gson.JsonSyntaxException;
18 import com.google.gson.stream.JsonReader;
19 import com.google.gson.stream.JsonToken;
20 import com.google.gson.stream.MalformedJsonException;
22 import java.io.Closeable;
23 import java.io.EOFException;
24 import java.io.Flushable;
25 import java.io.IOException;
27 import java.security.InvalidParameterException;
28 import java.util.ArrayDeque;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Deque;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.List;
37 import org.opendaylight.yangtools.concepts.Codec;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
40 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO;
41 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestCodecFactory;
42 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestUtil;
43 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestUtil.PrefixMapingFromJson;
44 import org.opendaylight.yangtools.yang.data.codec.gson.helpers.SchemaContextUtils;
45 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
47 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
48 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
49 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
53 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
55 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
58 * This class parses JSON elements from a GSON JsonReader. It disallows multiple elements of the same name unlike the
59 * default GSON JsonParser.
62 public final class JsonParserStream implements Closeable, Flushable {
63 private static final Splitter COLON_SPLITTER = Splitter.on(':');
65 private final Deque<URI> namespaces = new ArrayDeque<>();
66 private final NormalizedNodeStreamWriter writer;
67 private final SchemaContextUtils utils;
68 private final RestCodecFactory codecs;
69 private final SchemaContext schema;
71 private JsonParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
72 this.schema = Preconditions.checkNotNull(schemaContext);
73 this.utils = SchemaContextUtils.create(schemaContext);
74 this.writer = Preconditions.checkNotNull(writer);
75 this.codecs = RestCodecFactory.create(utils);
78 public static JsonParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
79 return new JsonParserStream(writer, schemaContext);
82 public JsonParserStream parse(final JsonReader reader) throws JsonIOException, JsonSyntaxException {
83 // code copied from gson's JsonParser and Stream classes
85 boolean lenient = reader.isLenient();
86 reader.setLenient(true);
87 boolean isEmpty = true;
91 CompositeNodeDataWithSchema compositeNodeDataWithSchema = new CompositeNodeDataWithSchema(schema);
92 read(reader, compositeNodeDataWithSchema);
93 compositeNodeDataWithSchema.writeToStream(writer);
96 // return read(reader);
97 } catch (EOFException e) {
100 // return JsonNull.INSTANCE;
102 // The stream ended prematurely so it is likely a syntax error.
103 throw new JsonSyntaxException(e);
104 } catch (MalformedJsonException e) {
105 throw new JsonSyntaxException(e);
106 } catch (IOException e) {
107 throw new JsonIOException(e);
108 } catch (NumberFormatException e) {
109 throw new JsonSyntaxException(e);
110 } catch (StackOverflowError | OutOfMemoryError e) {
111 throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
113 reader.setLenient(lenient);
117 public void read(final JsonReader in, final AbstractNodeDataWithSchema parent) throws IOException {
119 final JsonToken peek = in.peek();
120 Optional<String> value = Optional.absent();
124 value = Optional.of(in.nextString());
127 value = Optional.of(Boolean.toString(in.nextBoolean()));
131 value = Optional.of((String) null);
136 if (value.isPresent()) {
137 final Object translatedValue = translateValueByType(value.get(), parent.getSchema());
138 ((SimpleNodeDataWithSchema) parent).setValue(translatedValue);
144 while (in.hasNext()) {
145 AbstractNodeDataWithSchema newChild = null;
146 if (parent instanceof ListNodeDataWithSchema) {
147 newChild = new ListEntryNodeDataWithSchema(parent.getSchema());
148 ((CompositeNodeDataWithSchema) parent).addChild(newChild);
149 } else if (parent instanceof LeafListNodeDataWithSchema) {
150 newChild = new LeafListEntryNodeDataWithSchema(parent.getSchema());
151 ((CompositeNodeDataWithSchema) parent).addChild(newChild);
158 Set<String> namesakes = new HashSet<>();
160 while (in.hasNext()) {
161 final String jsonElementName = in.nextName();
162 final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName);
163 final String localName = namespaceAndName.getName();
164 addNamespace(namespaceAndName.getUri());
165 if (namesakes.contains(jsonElementName)) {
166 throw new JsonSyntaxException("Duplicate name " + jsonElementName + " in JSON input.");
168 namesakes.add(jsonElementName);
169 final Deque<DataSchemaNode> childDataSchemaNodes = findSchemaNodeByNameAndNamespace(parent.getSchema(),
170 localName, getCurrentNamespace());
171 if (childDataSchemaNodes.isEmpty()) {
172 throw new IllegalStateException("Schema for node with name " + localName + " and namespace "
173 + getCurrentNamespace() + " doesn't exist.");
176 AbstractNodeDataWithSchema newChild;
177 newChild = ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes);
178 // FIXME:anyxml data shouldn't be skipped but should be loaded somehow. will be specified after 17AUG2014
179 if (newChild instanceof AnyXmlNodeDataWithSchema) {
195 private Object translateValueByType(final String value, final DataSchemaNode node) {
196 final TypeDefinition<? extends Object> typeDefinition = typeDefinition(node);
197 if (typeDefinition == null) {
201 final Object inputValue;
202 if (typeDefinition instanceof IdentityrefTypeDefinition) {
203 inputValue = valueAsIdentityRef(value);
204 } else if (typeDefinition instanceof InstanceIdentifierTypeDefinition) {
205 inputValue = valueAsInstanceIdentifier(value);
210 // FIXME: extract this as a cacheable context?
211 final Codec<Object, Object> codec = codecs.codecFor(typeDefinition);
215 return codec.deserialize(inputValue);
218 private static TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
219 TypeDefinition<?> baseType = null;
220 if (node instanceof LeafListSchemaNode) {
221 baseType = ((LeafListSchemaNode) node).getType();
222 } else if (node instanceof LeafSchemaNode) {
223 baseType = ((LeafSchemaNode) node).getType();
224 } else if (node instanceof AnyXmlSchemaNode) {
227 throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());
230 if (baseType != null) {
231 while (baseType.getBaseType() != null) {
232 baseType = baseType.getBaseType();
238 private static Object valueAsInstanceIdentifier(final String value) {
239 // it could be instance-identifier Built-In Type
240 if (!value.isEmpty() && value.charAt(0) == '/') {
241 IdentityValuesDTO resolvedValue = RestUtil.asInstanceIdentifier(value, new PrefixMapingFromJson());
242 if (resolvedValue != null) {
243 return resolvedValue;
246 throw new InvalidParameterException("Value for instance-identifier doesn't have correct format");
249 private static IdentityValuesDTO valueAsIdentityRef(final String value) {
250 // it could be identityref Built-In Type
251 URI namespace = getNamespaceFor(value);
252 if (namespace != null) {
253 return new IdentityValuesDTO(namespace.toString(), getLocalNameFor(value), null, value);
255 throw new InvalidParameterException("Value for identityref has to be in format moduleName:localName.");
258 private static URI getNamespaceFor(final String jsonElementName) {
259 final Iterator<String> it = COLON_SPLITTER.split(jsonElementName).iterator();
261 // The string needs to me in form "moduleName:localName"
263 final String maybeURI = it.next();
264 if (Iterators.size(it) == 1) {
265 return URI.create(maybeURI);
272 private static String getLocalNameFor(final String jsonElementName) {
273 final Iterator<String> it = COLON_SPLITTER.split(jsonElementName).iterator();
275 // The string needs to me in form "moduleName:localName"
276 final String ret = Iterators.get(it, 1, null);
277 return ret != null && !it.hasNext() ? ret : jsonElementName;
280 private void removeNamespace() {
284 private void addNamespace(final Optional<URI> namespace) {
285 if (!namespace.isPresent()) {
286 if (namespaces.isEmpty()) {
287 throw new IllegalStateException("Namespace has to be specified at top level.");
289 namespaces.push(namespaces.peek());
292 namespaces.push(namespace.get());
296 private NamespaceAndName resolveNamespace(final String childName) {
297 int lastIndexOfColon = childName.lastIndexOf(":");
298 String moduleNamePart = null;
299 String nodeNamePart = null;
300 URI namespace = null;
301 if (lastIndexOfColon != -1) {
302 moduleNamePart = childName.substring(0, lastIndexOfColon);
303 nodeNamePart = childName.substring(lastIndexOfColon + 1);
304 namespace = utils.findNamespaceByModuleName(moduleNamePart);
306 nodeNamePart = childName;
309 Optional<URI> namespaceOpt = namespace == null ? Optional.<URI> absent() : Optional.of(namespace);
310 return new NamespaceAndName(nodeNamePart, namespaceOpt);
313 private URI getCurrentNamespace() {
314 return namespaces.peek();
318 * Returns stack of schema nodes via which it was necessary to prass to get schema node with specified
319 * {@code childName} and {@code namespace}
321 * @param dataSchemaNode
324 * @return stack of schema nodes via which it was passed through. If found schema node is dirrect child then stack
325 * contains only one node. If it is found under choice and case then stack should conains 2*n+1 element
326 * (where n is number of choices through it was passed)
328 private Deque<DataSchemaNode> findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode,
329 final String childName, final URI namespace) {
330 final Deque<DataSchemaNode> result = new ArrayDeque<>();
331 List<ChoiceNode> childChoices = new ArrayList<>();
332 if (dataSchemaNode instanceof DataNodeContainer) {
333 for (DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
334 if (childNode instanceof ChoiceNode) {
335 childChoices.add((ChoiceNode) childNode);
337 final QName childQName = childNode.getQName();
338 if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) {
339 result.push(childNode);
345 // try to find data schema node in choice (looking for first match)
346 for (ChoiceNode choiceNode : childChoices) {
347 for (ChoiceCaseNode concreteCase : choiceNode.getCases()) {
348 Deque<DataSchemaNode> resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName,
350 if (!resultFromRecursion.isEmpty()) {
351 resultFromRecursion.push(concreteCase);
352 resultFromRecursion.push(choiceNode);
353 return resultFromRecursion;
360 private static class NamespaceAndName {
361 private final Optional<URI> uri;
362 private final String name;
364 public NamespaceAndName(final String name, final Optional<URI> uri) {
369 public String getName() {
373 public Optional<URI> getUri() {
379 public void flush() throws IOException {
384 public void close() throws IOException {