+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
+import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON})
+public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader<PATCHContext> {
+
+ private final static Logger LOG = LoggerFactory.getLogger(JsonToPATCHBodyReader.class);
+ private String patchId;
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public PATCHContext readFrom(Class<PATCHContext> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
+ try {
+ return readFrom(getInstanceIdentifierContext(), entityStream);
+ } catch (final Exception e) {
+ throw propagateExceptionAs(e);
+ }
+ }
+
+ private static RuntimeException propagateExceptionAs(Exception e) throws RestconfDocumentedException {
+ if(e instanceof RestconfDocumentedException) {
+ throw (RestconfDocumentedException)e;
+ }
+
+ if(e instanceof ResultAlreadySetException) {
+ LOG.debug("Error parsing json input:", e);
+
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+ }
+
+ throw new RestconfDocumentedException("Error parsing json input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+
+ public PATCHContext readFrom(final String uriPath, final InputStream entityStream) throws
+ RestconfDocumentedException {
+ try {
+ return readFrom(ControllerContext.getInstance().toInstanceIdentifier(uriPath), entityStream);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private PATCHContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream) throws IOException {
+ if (entityStream.available() < 1) {
+ return new PATCHContext(path, null, null);
+ }
+
+ final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream));
+ final List<PATCHEntity> resultList = read(jsonReader, path);
+ jsonReader.close();
+
+ return new PATCHContext(path, resultList, patchId);
+ }
+
+ private List<PATCHEntity> read(final JsonReader in, InstanceIdentifierContext path) throws
+ IOException {
+
+ boolean inEdit = false;
+ boolean inValue = false;
+ String operation = null;
+ String target = null;
+ String editId = null;
+ List<PATCHEntity> resultCollection = new ArrayList<>();
+
+ while (in.hasNext()) {
+ switch (in.peek()) {
+ case STRING:
+ case NUMBER:
+ in.nextString();
+ break;
+ case BOOLEAN:
+ Boolean.toString(in.nextBoolean());
+ break;
+ case NULL:
+ in.nextNull();
+ break;
+ case BEGIN_ARRAY:
+ in.beginArray();
+ break;
+ case BEGIN_OBJECT:
+ if (inEdit && operation != null & target != null & inValue) {
+ //let's do the stuff - find out target node
+// StringInstanceIdentifierCodec codec = new StringInstanceIdentifierCodec(path
+// .getSchemaContext());
+// if (path.getInstanceIdentifier().toString().equals("/")) {
+// final YangInstanceIdentifier deserialized = codec.deserialize(target);
+// }
+ DataSchemaNode targetNode = ((DataNodeContainer)(path.getSchemaNode())).getDataChildByName
+ (target.replace("/", ""));
+ if (targetNode == null) {
+ LOG.debug("Target node {} not found in path {} ", target, path.getSchemaNode());
+ throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE);
+ } else {
+
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ //keep on parsing json from place where target points
+ final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(),
+ path.getSchemaNode());
+ jsonParser.parse(in);
+
+ final YangInstanceIdentifier targetII = path.getInstanceIdentifier().node(targetNode.getQName());
+ resultCollection.add(new PATCHEntity(editId, operation, targetII, resultHolder.getResult
+ ()));
+ inValue = false;
+
+ operation = null;
+ target = null;
+ }
+ in.endObject();
+ } else {
+ in.beginObject();
+ }
+ break;
+ case END_DOCUMENT:
+ break;
+ case NAME:
+ final String name = in.nextName();
+
+ switch (name) {
+ case "edit" : inEdit = true;
+ break;
+ case "operation" : operation = in.nextString();
+ break;
+ case "target" : target = in.nextString();
+ break;
+ case "value" : inValue = true;
+ break;
+ case "patch-id" : patchId = in.nextString();
+ break;
+ case "edit-id" : editId = in.nextString();
+ break;
+ }
+ break;
+ case END_OBJECT:
+ in.endObject();
+ break;
+ case END_ARRAY:
+ in.endArray();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return ImmutableList.copyOf(resultCollection);
+ }
+
+ private class StringInstanceIdentifierCodec extends AbstractStringInstanceIdentifierCodec {
+
+ private final DataSchemaContextTree dataContextTree;
+ private final SchemaContext context;
+
+ StringInstanceIdentifierCodec(SchemaContext context) {
+ this.context = Preconditions.checkNotNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ }
+
+ @Nonnull
+ @Override
+ protected DataSchemaContextTree getDataContextTree() {
+ return dataContextTree;
+ }
+
+ @Nullable
+ @Override
+ protected String prefixForNamespace(@Nonnull URI namespace) {
+ final Module module = context.findModuleByNamespaceAndRevision(namespace, null);
+ return module == null ? null : module.getName();
+ }
+
+ @Nullable
+ @Override
+ protected QName createQName(@Nonnull String prefix, @Nonnull String localName) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ }
+}