Split Restconf implementations (draft02 and RFC) - move providers 71/62871/6
authorJakub Toth <jakub.toth@pantheon.tech>
Thu, 7 Sep 2017 21:49:11 +0000 (23:49 +0200)
committerJakub Toth <jakub.toth@pantheon.tech>
Wed, 13 Sep 2017 10:51:08 +0000 (12:51 +0200)
Change-Id: Iab075f8d91001a646783f35aa5fe1550d811d01b
Signed-off-by: Jakub Toth <jakub.toth@pantheon.tech>
30 files changed:
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/AbstractIdentifierAwareJaxRsProvider.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/AbstractNormalizedNodeBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/AbstractToPatchBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/JsonNormalizedNodeBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/JsonToPatchBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/NormalizedNodeJsonBodyWriter.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/NormalizedNodeXmlBodyWriter.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/ParameterAwareNormalizedNodeWriter.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/StringModuleInstanceIdentifierCodec.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/XmlNormalizedNodeBodyReader.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/restconf/jersey/providers/XmlToPatchBodyReader.java
restconf/restconf-nb-rfc8040/pom.xml
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/codecs/StringModuleInstanceIdentifierCodec.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/DOMMountPointServiceHandler.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyReader.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/ParameterAwareNormalizedNodeWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyReader.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/api/RestconfNormalizedNodeWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractToPatchBodyReader.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonToPatchBodyReader.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/PatchJsonBodyWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/PatchXmlBodyWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlToPatchBodyReader.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/schema/SchemaExportContentYangBodyWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/schema/SchemaExportContentYinBodyWriter.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractIdentifierAwareJaxRsProvider.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractNormalizedNodeBodyReader.java [new file with mode: 0644]

index 1c150b58fb34a9bc5b0a6320b5e6d109f4d68ecb..b313911df734fe4173fb423a304831397b51c968 100644 (file)
@@ -27,6 +27,12 @@ import org.opendaylight.restconf.RestConnectorProvider;
 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
 
+/**
+ * JaxRd identifier aware provider.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 public abstract class AbstractIdentifierAwareJaxRsProvider<T> implements MessageBodyReader<T> {
 
     @Context
index 1a4e13af4e0c7e357137a9e809c70b9bf3ffa554..4614b48ef415461bdc2ae4533db795b812c99c20 100644 (file)
@@ -16,8 +16,10 @@ import org.opendaylight.restconf.common.context.NormalizedNodeContext;
 /**
  * Common superclass for readers producing {@link NormalizedNodeContext}.
  *
+ * @deprecated move to splitted module restconf-nb-rfc8040
  * @author Robert Varga
  */
+@Deprecated
 @Beta
 public abstract class AbstractNormalizedNodeBodyReader
         extends AbstractIdentifierAwareJaxRsProvider<NormalizedNodeContext> {
index 74be3b4a3791298da395c048835167d8af6cdbb3..c2a24b6000a55c66ff2fcbbfee499ede3bd5ac21 100644 (file)
@@ -13,8 +13,10 @@ import org.opendaylight.restconf.common.patch.PatchContext;
 /**
  * Common superclass for readers producing {@link PatchContext}.
  *
+ * @deprecated move to splitted module restconf-nb-rfc8040
  * @author Robert Varga
  */
+@Deprecated
 abstract class AbstractToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider<PatchContext> {
 
     @Override
index e176bbd828003f728ddbe52f7b735b7b8f732473..f234a550929ae34dabede36164a7be0114142578 100644 (file)
@@ -44,6 +44,12 @@ import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Reader of Json to NormalizedNode.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 @Provider
 @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, MediaType.APPLICATION_JSON })
 public class JsonNormalizedNodeBodyReader extends AbstractNormalizedNodeBodyReader {
index c10db1a70f9f08011ebd1e6c64b40ab58147f518..8f0ea15a5f9a8e2496d97a228d63beb24d4c3259 100644 (file)
@@ -45,6 +45,12 @@ import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Patch reader.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 @Provider
 @Consumes({Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON})
 public class JsonToPatchBodyReader extends AbstractToPatchBodyReader {
index a0ae624f3b034b9a369e0d39b90a5f4558bd9694..d5f23f72a4f85b1b6b9aa774cb6fc0fb35de11e7 100644 (file)
@@ -46,6 +46,12 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
+/**
+ * Writer NormalizedNode to Json.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 @Provider
 @Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, MediaType.APPLICATION_JSON })
 public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter<NormalizedNodeContext> {
index 4a5674053dbc27122684d90d219a9e8df27b5494..bf8eb6a986cb230a7fb1c4ab8f3215294eb680ac 100644 (file)
@@ -44,6 +44,12 @@ import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
+/**
+ * Writer of NormalizedNode to XML.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 @Provider
 @Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
 public class NormalizedNodeXmlBodyWriter implements MessageBodyWriter<NormalizedNodeContext> {
index c86bd6fcb3d3c1868c869f5d2587c4d1f2f67526..7b0592fb0e9c7df06c995f9504276a0ef0a8a34f 100644 (file)
@@ -42,11 +42,13 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This is an experimental iterator over a {@link NormalizedNode}. This is essentially
- * the opposite of a {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over
- * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows
- * us to write multiple nodes.
+ * This is an experimental iterator over a {@link NormalizedNode}. This is essentially the opposite of a
+ * {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over the backing data, this
+ * encapsulates a {@link NormalizedNodeStreamWriter} and allows us to write multiple nodes.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
  */
+@Deprecated
 @Beta
 public class ParameterAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
     private static final QName ROOT_DATA_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "data");
index 06f006cf9fd774ad7a2570c55b2c915b470bca8b..b58a623a8062193e8b978fb2ad12ee993f964b76 100644 (file)
@@ -17,13 +17,19 @@ import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
+/**
+ * Module instance identifier codec.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
 
     private final DataSchemaContextTree dataContextTree;
     private final SchemaContext context;
     private final String defaultPrefix;
 
-    public StringModuleInstanceIdentifierCodec(SchemaContext context) {
+    public StringModuleInstanceIdentifierCodec(final SchemaContext context) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = "";
index 15d63c423daeac223ec9a22e742bad6cbfe3d198..1345b37e309e917d248c3207978a3aeca6a2fffc 100644 (file)
@@ -57,6 +57,12 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
+/**
+ * Reader of XML to NormalizedNode.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 @Provider
 @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
 public class XmlNormalizedNodeBodyReader extends AbstractNormalizedNodeBodyReader {
index f88bf069b98644ea80149236deced760e7c112e1..fc74935120045ef7da0f41356e4cceb3f8e8cf67 100644 (file)
@@ -57,6 +57,12 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
+/**
+ * Reader of XML to Patch.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
 @Provider
 @Consumes({Rfc8040.MediaTypes.PATCH + RestconfConstants.XML})
 public class XmlToPatchBodyReader extends AbstractToPatchBodyReader {
index 4ee1330b54fce9223cf896100ce4a6ff93e8347c..e202a0b5676dc469b3bd07f91b11292fd797fdb3 100644 (file)
       <groupId>org.opendaylight.yangtools</groupId>
       <artifactId>yang-test-util</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-model-export</artifactId>
+    </dependency>
 
     <dependency>
       <groupId>org.opendaylight.mdsal.model</groupId>
       <version>3.0</version>
     </dependency>
 
+    <dependency>
+      <groupId>net.java.dev.stax-utils</groupId>
+      <artifactId>stax-utils</artifactId>
+    </dependency>
+
     <!-- Testing Dependencies -->
     <dependency>
       <groupId>org.glassfish.jersey.test-framework.providers</groupId>
index 1fc2d83b343a965ac1a5922aa2322cdaeba23601..69d26a5a67b99739e6d8101c75ed07ca7ce24392 100644 (file)
@@ -23,13 +23,13 @@ public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStr
     private final SchemaContext context;
     private final String defaultPrefix;
 
-    public StringModuleInstanceIdentifierCodec(SchemaContext context) {
+    public StringModuleInstanceIdentifierCodec(final SchemaContext context) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = "";
     }
 
-    StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
+    public StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = defaultPrefix;
@@ -46,7 +46,7 @@ public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStr
 
     @Nonnull
     @Override
-    protected DataSchemaContextTree getDataContextTree() {
+    public DataSchemaContextTree getDataContextTree() {
         return this.dataContextTree;
     }
 
index 5aa930c77ea7c3da605f3d07c68e0b6aa95cb1f9..ecb67cc09187fec4c04ad216e0ccfb33bf419320 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.handlers;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
 
@@ -17,6 +18,7 @@ import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
 public class DOMMountPointServiceHandler implements Handler<DOMMountPointService> {
 
     private final DOMMountPointService domMountPointService;
+    private static DOMMountPointService actualDomMountPointService;
 
     /**
      * Prepare mount point service for Restconf services.
@@ -27,6 +29,7 @@ public class DOMMountPointServiceHandler implements Handler<DOMMountPointService
     public DOMMountPointServiceHandler(final DOMMountPointService domMountPointService) {
         Preconditions.checkNotNull(domMountPointService);
         this.domMountPointService = domMountPointService;
+        actualDomMountPointService = domMountPointService;
     }
 
     @Override
@@ -34,4 +37,8 @@ public class DOMMountPointServiceHandler implements Handler<DOMMountPointService
         return this.domMountPointService;
     }
 
+    public static Optional<DOMMountPointService> getActualMountPointService() {
+        return Optional.fromNullable(actualDomMountPointService);
+    }
+
 }
index 0db960f9db5142e36fea3d061173a39dfd76ab53..b3719b5e331e137092f80e0aff0051c46a8a3f18 100644 (file)
@@ -38,6 +38,7 @@ public class SchemaContextHandler implements SchemaContextListenerHandler {
 
     private final TransactionChainHandler transactionChainHandler;
     private SchemaContext context;
+    private static SchemaContext actualSchemaContext;
 
     private int moduleSetId;
 
@@ -49,6 +50,7 @@ public class SchemaContextHandler implements SchemaContextListenerHandler {
     public SchemaContextHandler(final TransactionChainHandler transactionChainHandler) {
         this.transactionChainHandler = transactionChainHandler;
         this.moduleSetId = 0;
+        actualSchemaContext = null;
     }
 
     @Override
@@ -56,6 +58,9 @@ public class SchemaContextHandler implements SchemaContextListenerHandler {
         Preconditions.checkNotNull(context);
         this.context = null;
         this.context = context;
+
+        actualSchemaContext = context;
+
         this.moduleSetId++;
         final Module ietfYangLibraryModule =
                 context.findModuleByNamespaceAndRevision(IetfYangLibrary.URI_MODULE, IetfYangLibrary.DATE);
@@ -75,6 +80,10 @@ public class SchemaContextHandler implements SchemaContextListenerHandler {
         return this.context;
     }
 
+    public static SchemaContext getActualSchemaContext() {
+        return actualSchemaContext;
+    }
+
     private void putData(
             final NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>> normNode) {
         final DOMDataWriteTransaction wTx = this.transactionChainHandler.get().newWriteOnlyTransaction();
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyReader.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyReader.java
new file mode 100644 (file)
index 0000000..d604f53
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers;
+
+import com.google.common.collect.Iterables;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.spi.AbstractNormalizedNodeBodyReader;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+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.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, MediaType.APPLICATION_JSON })
+public class JsonNormalizedNodeBodyReader extends AbstractNormalizedNodeBodyReader {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JsonNormalizedNodeBodyReader.class);
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @Override
+    protected NormalizedNodeContext readBody(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException, WebApplicationException {
+        try {
+            return readFrom(path, entityStream, isPost());
+        } catch (final Exception e) {
+            propagateExceptionAs(e);
+            return null;
+        }
+    }
+
+    public static NormalizedNodeContext readFrom(
+            final InstanceIdentifierContext<?> path, final InputStream entityStream, final boolean isPost)
+            throws IOException {
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+        final SchemaNode parentSchema;
+        if (isPost) {
+            parentSchema = path.getSchemaNode();
+        } else if (path.getSchemaNode() instanceof SchemaContext) {
+            parentSchema = path.getSchemaContext();
+        } else {
+            if (SchemaPath.ROOT.equals(path.getSchemaNode().getPath().getParent())) {
+                parentSchema = path.getSchemaContext();
+            } else {
+                parentSchema = SchemaContextUtil
+                        .findDataSchemaNode(path.getSchemaContext(), path.getSchemaNode().getPath().getParent());
+            }
+        }
+
+        final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), parentSchema);
+        final JsonReader reader = new JsonReader(new InputStreamReader(entityStream));
+        jsonParser.parse(reader);
+
+        NormalizedNode<?, ?> result = resultHolder.getResult();
+        final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
+        InstanceIdentifierContext<? extends SchemaNode> newIIContext;
+
+        while (result instanceof AugmentationNode || result instanceof ChoiceNode) {
+            final Object childNode = ((DataContainerNode<?>) result).getValue().iterator().next();
+            if (isPost) {
+                iiToDataList.add(result.getIdentifier());
+            }
+            result = (NormalizedNode<?, ?>) childNode;
+        }
+
+        if (isPost) {
+            if (result instanceof MapEntryNode) {
+                iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(result.getNodeType()));
+                iiToDataList.add(result.getIdentifier());
+            } else {
+                iiToDataList.add(result.getIdentifier());
+            }
+        } else {
+            if (result instanceof MapNode) {
+                result = Iterables.getOnlyElement(((MapNode) result).getValue());
+            }
+        }
+
+        final YangInstanceIdentifier fullIIToData = YangInstanceIdentifier.create(Iterables.concat(
+                path.getInstanceIdentifier().getPathArguments(), iiToDataList));
+
+        newIIContext = new InstanceIdentifierContext<>(fullIIToData, path.getSchemaNode(), path.getMountPoint(),
+                path.getSchemaContext());
+
+        return new NormalizedNodeContext(newIIContext, result);
+    }
+
+    private static void propagateExceptionAs(final Exception exception) throws RestconfDocumentedException {
+        if (exception instanceof RestconfDocumentedException) {
+            throw (RestconfDocumentedException)exception;
+        }
+
+        if (exception instanceof ResultAlreadySetException) {
+            LOG.debug("Error parsing json input:", exception);
+
+            throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. "
+                    + "Are you creating multiple resources/subresources in POST request?", exception);
+        }
+
+        LOG.debug("Error parsing json input", exception);
+
+        throw new RestconfDocumentedException("Error parsing input: " + exception.getMessage(), ErrorType.PROTOCOL,
+                ErrorTag.MALFORMED_MESSAGE, exception);
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java
new file mode 100644 (file)
index 0000000..e3c8b59
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+@Provider
+@Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, MediaType.APPLICATION_JSON })
+public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter<NormalizedNodeContext> {
+
+    private static final int DEFAULT_INDENT_SPACES_NUM = 2;
+
+    @Override
+    public boolean isWriteable(final Class<?> type,
+                               final Type genericType,
+                               final Annotation[] annotations,
+                               final MediaType mediaType) {
+        return type.equals(NormalizedNodeContext.class);
+    }
+
+    @Override
+    public long getSize(final NormalizedNodeContext context,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final NormalizedNodeContext context,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders,
+                        final OutputStream entityStream) throws IOException, WebApplicationException {
+        final NormalizedNode<?, ?> data = context.getData();
+        if (data == null) {
+            return;
+        }
+
+        @SuppressWarnings("unchecked")
+        final InstanceIdentifierContext<SchemaNode> identifierCtx =
+                (InstanceIdentifierContext<SchemaNode>) context.getInstanceIdentifierContext();
+        final SchemaPath path = identifierCtx.getSchemaNode().getPath();
+        final JsonWriter jsonWriter = createJsonWriter(entityStream,
+                context.getWriterParameters().isPrettyPrint());
+
+        jsonWriter.beginObject();
+        writeNormalizedNode(jsonWriter, path, identifierCtx, data,
+                context.getWriterParameters().getDepth(), context.getWriterParameters().getFields());
+        jsonWriter.endObject();
+        jsonWriter.flush();
+    }
+
+    private static void writeNormalizedNode(final JsonWriter jsonWriter,
+            final SchemaPath path, final InstanceIdentifierContext<SchemaNode> context, final NormalizedNode<?, ?> data,
+            final Integer depth, final List<Set<QName>> fields) throws IOException {
+        final RestconfNormalizedNodeWriter nnWriter;
+
+        if (context.getSchemaNode() instanceof RpcDefinition) {
+            /*
+             *  RpcDefinition is not supported as initial codec in JSONStreamWriter,
+             *  so we need to emit initial output declaration..
+             */
+            nnWriter = createNormalizedNodeWriter(
+                    context,
+                    ((RpcDefinition) context.getSchemaNode()).getOutput().getPath(),
+                    jsonWriter,
+                    depth,
+                    fields);
+            jsonWriter.name("output");
+            jsonWriter.beginObject();
+            writeChildren(nnWriter, (ContainerNode) data);
+            jsonWriter.endObject();
+        } else {
+            if (SchemaPath.ROOT.equals(path)) {
+                nnWriter = createNormalizedNodeWriter(context, path, jsonWriter, depth, fields);
+            } else {
+                nnWriter = createNormalizedNodeWriter(context, path.getParent(), jsonWriter, depth, fields);
+            }
+
+            if (data instanceof MapEntryNode) {
+                // Restconf allows returning one list item. We need to wrap it
+                // in map node in order to serialize it properly
+                nnWriter.write(
+                        ImmutableNodes.mapNodeBuilder(data.getNodeType()).withChild((MapEntryNode) data).build());
+            } else {
+                nnWriter.write(data);
+            }
+        }
+
+        nnWriter.flush();
+    }
+
+    private static void writeChildren(final RestconfNormalizedNodeWriter nnWriter,
+                                      final ContainerNode data) throws IOException {
+        for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
+            nnWriter.write(child);
+        }
+    }
+
+    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(
+            final InstanceIdentifierContext<SchemaNode> context, final SchemaPath path, final JsonWriter jsonWriter,
+            final Integer depth, final List<Set<QName>> fields) {
+
+        final SchemaNode schema = context.getSchemaNode();
+        final JSONCodecFactory codecs = getCodecFactory(context);
+
+        final URI initialNs;
+        if (schema instanceof DataSchemaNode
+                && !((DataSchemaNode)schema).isAugmenting()
+                && !(schema instanceof SchemaContext)) {
+            initialNs = schema.getQName().getNamespace();
+        } else if (schema instanceof RpcDefinition) {
+            initialNs = schema.getQName().getNamespace();
+        } else {
+            initialNs = null;
+        }
+        final NormalizedNodeStreamWriter streamWriter = JSONNormalizedNodeStreamWriter.createNestedWriter(
+                codecs, path, initialNs, jsonWriter);
+        return ParameterAwareNormalizedNodeWriter.forStreamWriter(streamWriter, depth, fields);
+    }
+
+    private static JsonWriter createJsonWriter(final OutputStream entityStream, final boolean prettyPrint) {
+        if (prettyPrint) {
+            return JsonWriterFactory.createJsonWriter(
+                    new OutputStreamWriter(entityStream, StandardCharsets.UTF_8), DEFAULT_INDENT_SPACES_NUM);
+        }
+        return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8));
+    }
+
+    private static JSONCodecFactory getCodecFactory(final InstanceIdentifierContext<?> context) {
+        // TODO: Performance: Cache JSON Codec factory and schema context
+        return JSONCodecFactory.getShared(context.getSchemaContext());
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java
new file mode 100644 (file)
index 0000000..740bc19
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Set;
+import javanet.staxutils.IndentingXMLStreamWriter;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.XMLConstants;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+@Provider
+@Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+public class NormalizedNodeXmlBodyWriter implements MessageBodyWriter<NormalizedNodeContext> {
+
+    private static final XMLOutputFactory XML_FACTORY;
+
+    static {
+        XML_FACTORY = XMLOutputFactory.newFactory();
+        XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+    }
+
+    @Override
+    public boolean isWriteable(final Class<?> type,
+                               final Type genericType,
+                               final Annotation[] annotations,
+                               final MediaType mediaType) {
+        return type.equals(NormalizedNodeContext.class);
+    }
+
+    @Override
+    public long getSize(final NormalizedNodeContext context,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final NormalizedNodeContext context,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders,
+                        final OutputStream entityStream) throws IOException, WebApplicationException {
+        final InstanceIdentifierContext<?> pathContext = context.getInstanceIdentifierContext();
+        if (context.getData() == null) {
+            return;
+        }
+
+        XMLStreamWriter xmlWriter;
+        try {
+            xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
+            if (context.getWriterParameters().isPrettyPrint()) {
+                xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
+            }
+        } catch (final XMLStreamException | FactoryConfigurationError e) {
+            throw new IllegalStateException(e);
+        }
+        final NormalizedNode<?, ?> data = context.getData();
+        final SchemaPath schemaPath = pathContext.getSchemaNode().getPath();
+
+        writeNormalizedNode(xmlWriter, schemaPath, pathContext, data, context.getWriterParameters().getDepth(),
+                context.getWriterParameters().getFields());
+    }
+
+    private static void writeNormalizedNode(final XMLStreamWriter xmlWriter,
+            final SchemaPath path, final InstanceIdentifierContext<?> pathContext, final NormalizedNode<?, ?> data,
+            final Integer depth, final List<Set<QName>> fields) throws IOException {
+        final RestconfNormalizedNodeWriter nnWriter;
+        final SchemaContext schemaCtx = pathContext.getSchemaContext();
+
+        if (pathContext.getSchemaNode() instanceof RpcDefinition) {
+            /*
+             *  RpcDefinition is not supported as initial codec in XMLStreamWriter,
+             *  so we need to emit initial output declaration..
+             */
+            nnWriter = createNormalizedNodeWriter(
+                    xmlWriter,
+                    schemaCtx,
+                    ((RpcDefinition) pathContext.getSchemaNode()).getOutput().getPath(),
+                    depth,
+                    fields);
+            writeElements(xmlWriter, nnWriter, (ContainerNode) data);
+        } else {
+            if (SchemaPath.ROOT.equals(path)) {
+                nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, path, depth, fields);
+            } else {
+                nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, path.getParent(), depth, fields);
+            }
+
+            if (data instanceof MapEntryNode) {
+                // Restconf allows returning one list item. We need to wrap it
+                // in map node in order to serialize it properly
+                nnWriter.write(ImmutableNodes.mapNodeBuilder(data.getNodeType()).addChild((MapEntryNode) data).build());
+            } else {
+                nnWriter.write(data);
+            }
+        }
+
+        nnWriter.flush();
+    }
+
+    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
+            final SchemaContext schemaContext, final SchemaPath schemaPath, final Integer depth,
+            final List<Set<QName>> fields) {
+        final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter
+                .create(xmlWriter, schemaContext, schemaPath);
+        return ParameterAwareNormalizedNodeWriter.forStreamWriter(xmlStreamWriter, depth, fields);
+    }
+
+    private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
+            final ContainerNode data) throws IOException {
+        try {
+            final QName name = data.getNodeType();
+            xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
+                    name.getLocalName(), name.getNamespace().toString());
+            xmlWriter.writeDefaultNamespace(name.getNamespace().toString());
+            for (final NormalizedNode<?,?> child : data.getValue()) {
+                nnWriter.write(child);
+            }
+            nnWriter.flush();
+            xmlWriter.writeEndElement();
+            xmlWriter.flush();
+        } catch (final XMLStreamException e) {
+            Throwables.propagate(e);
+        }
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/ParameterAwareNormalizedNodeWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/ParameterAwareNormalizedNodeWriter.java
new file mode 100644 (file)
index 0000000..55c404a
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers;
+
+import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is an experimental iterator over a {@link NormalizedNode}. This is essentially
+ * the opposite of a {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over
+ * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows
+ * us to write multiple nodes.
+ */
+@Beta
+public class ParameterAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
+    private static final QName ROOT_DATA_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "data");
+
+    private final NormalizedNodeStreamWriter writer;
+    private final Integer maxDepth;
+    protected final List<Set<QName>> fields;
+    protected int currentDepth = 0;
+
+    private ParameterAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final Integer maxDepth,
+                                               final List<Set<QName>> fields) {
+        this.writer = Preconditions.checkNotNull(writer);
+        this.maxDepth = maxDepth;
+        this.fields = fields;
+    }
+
+    protected final NormalizedNodeStreamWriter getWriter() {
+        return writer;
+    }
+
+    /**
+     * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
+     *
+     * @param writer Back-end writer
+     * @param maxDepth Maximal depth to write
+     * @param fields Selected child nodes to write
+     * @return A new instance.
+     */
+    public static ParameterAwareNormalizedNodeWriter forStreamWriter(
+            final NormalizedNodeStreamWriter writer, final Integer maxDepth, final List<Set<QName>> fields) {
+        return forStreamWriter(writer, true,  maxDepth, fields);
+    }
+
+    /**
+     * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple
+     * {@link #forStreamWriter(NormalizedNodeStreamWriter, Integer, List)}
+     * method, this allows the caller to switch off RFC6020 XML compliance, providing better
+     * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding
+     * to emit leaf nodes which participate in a list's key first and in the order in which
+     * they are defined in the key. For JSON, this requirement is completely relaxed and leaves
+     * can be ordered in any way we see fit. The former requires a bit of work: first a lookup
+     * for each key and then for each emitted node we need to check whether it was already
+     * emitted.
+     *
+     * @param writer Back-end writer
+     * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
+     * @param maxDepth Maximal depth to write
+     * @param fields Selected child nodes to write
+     * @return A new instance.
+     */
+    public static ParameterAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+                                                                     final boolean orderKeyLeaves,
+                                                                     final Integer maxDepth,
+                                                                     final List<Set<QName>> fields) {
+        return orderKeyLeaves ? new OrderedParameterAwareNormalizedNodeWriter(writer, maxDepth, fields)
+                : new ParameterAwareNormalizedNodeWriter(writer, maxDepth, fields);
+    }
+
+    /**
+     * Iterate over the provided {@link NormalizedNode} and emit write
+     * events to the encapsulated {@link NormalizedNodeStreamWriter}.
+     *
+     * @param node Node
+     * @return {@code ParameterAwareNormalizedNodeWriter}
+     * @throws IOException when thrown from the backing writer.
+     */
+    @Override
+    public final ParameterAwareNormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
+        if (wasProcessedAsCompositeNode(node)) {
+            return this;
+        }
+
+        if (wasProcessAsSimpleNode(node)) {
+            return this;
+        }
+
+        throw new IllegalStateException("It wasn't possible to serialize node " + node);
+    }
+
+    @Override
+    public void flush() throws IOException {
+        writer.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+        writer.flush();
+        writer.close();
+    }
+
+    /**
+     * Emit a best guess of a hint for a particular set of children. It evaluates the
+     * iterable to see if the size can be easily gotten to. If it is, we hint at the
+     * real number of child nodes. Otherwise we emit UNKNOWN_SIZE.
+     *
+     * @param children Child nodes
+     * @return Best estimate of the collection size required to hold all the children.
+     */
+    static final int childSizeHint(final Iterable<?> children) {
+        return children instanceof Collection ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
+    }
+
+    private boolean wasProcessAsSimpleNode(final NormalizedNode<?, ?> node) throws IOException {
+        if (node instanceof LeafSetEntryNode) {
+            if (selectedByParameters(node, false)) {
+                final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>) node;
+                if (writer instanceof NormalizedNodeStreamAttributeWriter) {
+                    ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(nodeAsLeafList.getNodeType(),
+                            nodeAsLeafList.getValue(), nodeAsLeafList.getAttributes());
+                } else {
+                    writer.leafSetEntryNode(nodeAsLeafList.getNodeType(), nodeAsLeafList.getValue());
+                }
+            }
+            return true;
+        } else if (node instanceof LeafNode) {
+            final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
+            if (writer instanceof NormalizedNodeStreamAttributeWriter) {
+                ((NormalizedNodeStreamAttributeWriter) writer).leafNode(
+                        nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes());
+            } else {
+                writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue());
+            }
+            return true;
+        } else if (node instanceof AnyXmlNode) {
+            final AnyXmlNode anyXmlNode = (AnyXmlNode)node;
+            writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue());
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check if node should be written according to parameters fields and depth.
+     * See <a href="https://tools.ietf.org/html/draft-ietf-netconf-restconf-18#page-49">Restconf draft</a>.
+     * @param node Node to be written
+     * @param mixinParent {@code true} if parent is mixin, {@code false} otherwise
+     * @return {@code true} if node will be written, {@code false} otherwise
+     */
+    protected boolean selectedByParameters(final NormalizedNode<?, ?> node, final boolean mixinParent) {
+        // nodes to be written are not limited by fields, only by depth
+        if (fields == null) {
+            return maxDepth == null || currentDepth < maxDepth;
+        }
+
+        // children of mixin nodes are never selected in fields but must be written if they are first in selected target
+        if (mixinParent && currentDepth == 0) {
+            return true;
+        }
+
+        // always write augmentation nodes
+        if (node instanceof AugmentationNode) {
+            return true;
+        }
+
+        // write only selected nodes
+        if (currentDepth > 0 && currentDepth <= fields.size()) {
+            return fields.get(currentDepth - 1).contains(node.getNodeType());
+        }
+
+        // after this depth only depth parameter is used to determine when to write node
+        return maxDepth == null || currentDepth < maxDepth;
+    }
+
+    /**
+     * Emit events for all children and then emit an endNode() event.
+     *
+     * @param children Child iterable
+     * @param mixinParent {@code true} if parent is mixin, {@code false} otherwise
+     * @return True
+     * @throws IOException when the writer reports it
+     */
+    protected final boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children,
+                                          final boolean mixinParent) throws IOException {
+        for (final NormalizedNode<?, ?> child : children) {
+            if (selectedByParameters(child, mixinParent)) {
+                write(child);
+            }
+        }
+        writer.endNode();
+        return true;
+    }
+
+    protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
+        if (selectedByParameters(mapEntryNode, false)) {
+            writeChildren(mapEntryNode.getValue(), false);
+        } else if (fields == null && maxDepth != null && currentDepth == maxDepth) {
+            writeOnlyKeys(mapEntryNode.getIdentifier().getKeyValues());
+        }
+        return true;
+    }
+
+    private void writeOnlyKeys(final Map<QName, Object> keyValues) throws IllegalArgumentException, IOException {
+        for (final Map.Entry<QName, Object> entry : keyValues.entrySet()) {
+            writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue());
+        }
+        writer.endNode();
+    }
+
+    protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
+        if (writer instanceof NormalizedNodeStreamAttributeWriter) {
+            ((NormalizedNodeStreamAttributeWriter) writer)
+                    .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
+        } else {
+            writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
+        }
+        currentDepth++;
+        writeMapEntryChildren(node);
+        currentDepth--;
+        return true;
+    }
+
+    private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
+        boolean processedAsCompositeNode = false;
+        if (node instanceof ContainerNode) {
+            final ContainerNode n = (ContainerNode) node;
+            if (!n.getNodeType().equals(ROOT_DATA_QNAME)) {
+                if (writer instanceof NormalizedNodeStreamAttributeWriter) {
+                    ((NormalizedNodeStreamAttributeWriter) writer).startContainerNode(
+                            n.getIdentifier(), childSizeHint(n.getValue()), n.getAttributes());
+                } else {
+                    writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()));
+                }
+                currentDepth++;
+                processedAsCompositeNode = writeChildren(n.getValue(), false);
+                currentDepth--;
+            } else {
+                // write child nodes of data root container
+                for (final NormalizedNode<?, ?> child : n.getValue()) {
+                    currentDepth++;
+                    if (selectedByParameters(child, false)) {
+                        write(child);
+                    }
+                    currentDepth--;
+                    processedAsCompositeNode = true;
+                }
+            }
+        } else if (node instanceof MapEntryNode) {
+            processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node);
+        } else if (node instanceof UnkeyedListEntryNode) {
+            final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
+            writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.getValue()));
+            currentDepth++;
+            processedAsCompositeNode = writeChildren(n.getValue(), false);
+            currentDepth--;
+        } else if (node instanceof ChoiceNode) {
+            final ChoiceNode n = (ChoiceNode) node;
+            writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue()));
+            processedAsCompositeNode = writeChildren(n.getValue(), true);
+        } else if (node instanceof AugmentationNode) {
+            final AugmentationNode n = (AugmentationNode) node;
+            writer.startAugmentationNode(n.getIdentifier());
+            processedAsCompositeNode = writeChildren(n.getValue(), true);
+        } else if (node instanceof UnkeyedListNode) {
+            final UnkeyedListNode n = (UnkeyedListNode) node;
+            writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue()));
+            processedAsCompositeNode = writeChildren(n.getValue(), false);
+        } else if (node instanceof OrderedMapNode) {
+            final OrderedMapNode n = (OrderedMapNode) node;
+            writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
+            processedAsCompositeNode = writeChildren(n.getValue(), true);
+        } else if (node instanceof MapNode) {
+            final MapNode n = (MapNode) node;
+            writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
+            processedAsCompositeNode = writeChildren(n.getValue(), true);
+        } else if (node instanceof LeafSetNode) {
+            final LeafSetNode<?> n = (LeafSetNode<?>) node;
+            if (node instanceof OrderedLeafSetNode) {
+                writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
+            } else {
+                writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
+            }
+            currentDepth++;
+            processedAsCompositeNode = writeChildren(n.getValue(), true);
+            currentDepth--;
+        }
+
+        return processedAsCompositeNode;
+    }
+
+    private static final class OrderedParameterAwareNormalizedNodeWriter extends ParameterAwareNormalizedNodeWriter {
+        private static final Logger LOG = LoggerFactory.getLogger(OrderedParameterAwareNormalizedNodeWriter.class);
+
+        OrderedParameterAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final Integer maxDepth,
+                                                  final List<Set<QName>> fields) {
+            super(writer, maxDepth, fields);
+        }
+
+        @Override
+        protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
+            final NormalizedNodeStreamWriter writer = getWriter();
+            if (writer instanceof NormalizedNodeStreamAttributeWriter) {
+                ((NormalizedNodeStreamAttributeWriter) writer).startMapEntryNode(
+                        node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
+            } else {
+                writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
+            }
+
+            final Set<QName> qnames = node.getIdentifier().getKeyValues().keySet();
+            // Write out all the key children
+            currentDepth++;
+            for (final QName qname : qnames) {
+                final Optional<? extends NormalizedNode<?, ?>> child = node.getChild(new NodeIdentifier(qname));
+                if (child.isPresent()) {
+                    if (selectedByParameters(child.get(), false)) {
+                        write(child.get());
+                    }
+                } else {
+                    LOG.info("No child for key element {} found", qname);
+                }
+            }
+            currentDepth--;
+
+            currentDepth++;
+            // Write all the rest
+            final boolean result =
+                    writeChildren(Iterables.filter(node.getValue(), input -> {
+                        if (input instanceof AugmentationNode) {
+                            return true;
+                        }
+                        if (!qnames.contains(input.getNodeType())) {
+                            return true;
+                        }
+
+                        LOG.debug("Skipping key child {}", input);
+                        return false;
+                    }), false);
+            currentDepth--;
+            return result;
+        }
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyReader.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyReader.java
new file mode 100644 (file)
index 0000000..dc5a1f2
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.spi.AbstractNormalizedNodeBodyReader;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
+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.SchemaUtils;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+@Provider
+@Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+public class XmlNormalizedNodeBodyReader extends AbstractNormalizedNodeBodyReader {
+    private static final Logger LOG = LoggerFactory.getLogger(XmlNormalizedNodeBodyReader.class);
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @Override
+    protected NormalizedNodeContext readBody(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException, WebApplicationException {
+        try {
+            final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
+            return parse(path,doc);
+        } catch (final RestconfDocumentedException e) {
+            throw e;
+        } catch (final Exception e) {
+            LOG.debug("Error parsing xml input", e);
+
+            throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+                    ErrorTag.MALFORMED_MESSAGE, e);
+        }
+    }
+
+    private NormalizedNodeContext parse(final InstanceIdentifierContext<?> pathContext, final Document doc)
+            throws XMLStreamException, IOException, ParserConfigurationException, SAXException, URISyntaxException {
+        final SchemaNode schemaNodeContext = pathContext.getSchemaNode();
+        DataSchemaNode schemaNode;
+        boolean isRpc = false;
+        if (schemaNodeContext instanceof RpcDefinition) {
+            schemaNode = ((RpcDefinition) schemaNodeContext).getInput();
+            isRpc = true;
+        } else if (schemaNodeContext instanceof DataSchemaNode) {
+            schemaNode = (DataSchemaNode) schemaNodeContext;
+        } else {
+            throw new IllegalStateException("Unknown SchemaNode");
+        }
+
+        final String docRootElm = doc.getDocumentElement().getLocalName();
+        final String docRootNamespace = doc.getDocumentElement().getNamespaceURI();
+        final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
+
+        if (isPost() && !isRpc) {
+            final Deque<Object> foundSchemaNodes = findPathToSchemaNodeByName(schemaNode, docRootElm, docRootNamespace);
+            if (foundSchemaNodes.isEmpty()) {
+                throw new IllegalStateException(String.format("Child \"%s\" was not found in parent schema node \"%s\"",
+                        docRootElm, schemaNode.getQName()));
+            }
+            while (!foundSchemaNodes.isEmpty()) {
+                final Object child = foundSchemaNodes.pop();
+                if (child instanceof AugmentationSchema) {
+                    final AugmentationSchema augmentSchemaNode = (AugmentationSchema) child;
+                    iiToDataList.add(SchemaUtils.getNodeIdentifierForAugmentation(augmentSchemaNode));
+                } else if (child instanceof DataSchemaNode) {
+                    schemaNode = (DataSchemaNode) child;
+                    iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(schemaNode.getQName()));
+                }
+            }
+        // PUT
+        } else if (!isRpc) {
+            final QName scQName = schemaNode.getQName();
+            Preconditions.checkState(
+                    docRootElm.equals(scQName.getLocalName())
+                            && docRootNamespace.equals(scQName.getNamespace().toASCIIString()),
+                    String.format("Not correct message root element \"%s\", should be \"%s\"",
+                            docRootElm, scQName));
+        }
+
+        NormalizedNode<?, ?> parsed;
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+        if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
+                || schemaNode instanceof LeafSchemaNode) {
+            final XmlParserStream xmlParser = XmlParserStream.create(writer, pathContext.getSchemaContext(),
+                    schemaNode);
+            xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
+            parsed = resultHolder.getResult();
+
+            // When parsing an XML source with a list root node
+            // the new XML parser always returns a MapNode with one MapEntryNode inside.
+            // However, the old XML parser returned a MapEntryNode directly in this place.
+            // Therefore we now have to extract the MapEntryNode from the parsed MapNode.
+            if (parsed instanceof MapNode) {
+                final MapNode mapNode = (MapNode) parsed;
+                // extracting the MapEntryNode
+                parsed = mapNode.getValue().iterator().next();
+            }
+
+            if (schemaNode instanceof  ListSchemaNode && isPost()) {
+                iiToDataList.add(parsed.getIdentifier());
+            }
+        } else {
+            LOG.warn("Unknown schema node extension {} was not parsed", schemaNode.getClass());
+            parsed = null;
+        }
+
+        final YangInstanceIdentifier fullIIToData = YangInstanceIdentifier.create(Iterables.concat(
+                pathContext.getInstanceIdentifier().getPathArguments(), iiToDataList));
+
+        final InstanceIdentifierContext<? extends SchemaNode> outIIContext = new InstanceIdentifierContext<>(
+                fullIIToData, pathContext.getSchemaNode(), pathContext.getMountPoint(), pathContext.getSchemaContext());
+
+        return new NormalizedNodeContext(outIIContext, parsed);
+    }
+
+    private static Deque<Object> findPathToSchemaNodeByName(final DataSchemaNode schemaNode, final String elementName,
+                                                            final String namespace) {
+        final Deque<Object> result = new ArrayDeque<>();
+        final ArrayList<ChoiceSchemaNode> choiceSchemaNodes = new ArrayList<>();
+        final Collection<DataSchemaNode> children = ((DataNodeContainer) schemaNode).getChildNodes();
+        for (final DataSchemaNode child : children) {
+            if (child instanceof ChoiceSchemaNode) {
+                choiceSchemaNodes.add((ChoiceSchemaNode) child);
+            } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)
+                    && child.getQName().getNamespace().toString().equalsIgnoreCase(namespace)) {
+                // add child to result
+                result.push(child);
+
+                // find augmentation
+                if (child.isAugmenting()) {
+                    final AugmentationSchema augment = findCorrespondingAugment(schemaNode, child);
+                    if (augment != null) {
+                        result.push(augment);
+                    }
+                }
+
+                // return result
+                return result;
+            }
+        }
+
+        for (final ChoiceSchemaNode choiceNode : choiceSchemaNodes) {
+            for (final ChoiceCaseNode caseNode : choiceNode.getCases()) {
+                final Deque<Object> resultFromRecursion = findPathToSchemaNodeByName(caseNode, elementName, namespace);
+                if (!resultFromRecursion.isEmpty()) {
+                    resultFromRecursion.push(choiceNode);
+                    if (choiceNode.isAugmenting()) {
+                        final AugmentationSchema augment = findCorrespondingAugment(schemaNode, choiceNode);
+                        if (augment != null) {
+                            resultFromRecursion.push(augment);
+                        }
+                    }
+                    return resultFromRecursion;
+                }
+            }
+        }
+        return result;
+    }
+
+    private static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent,
+                                                               final DataSchemaNode child) {
+        if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
+            for (final AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
+                final DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
+                if (childInAugmentation != null) {
+                    return augmentation;
+                }
+            }
+        }
+        return null;
+    }
+}
+
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/api/RestconfNormalizedNodeWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/api/RestconfNormalizedNodeWriter.java
new file mode 100644 (file)
index 0000000..37b32c1
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * 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.restconf.nb.rfc8040.jersey.providers.api;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public interface RestconfNormalizedNodeWriter extends Flushable, Closeable {
+
+    RestconfNormalizedNodeWriter write(NormalizedNode<?, ?> node) throws IOException;
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractToPatchBodyReader.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractToPatchBodyReader.java
new file mode 100644 (file)
index 0000000..e2e7fdc
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.restconf.nb.rfc8040.jersey.providers.patch;
+
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.spi.AbstractIdentifierAwareJaxRsProvider;
+
+/**
+ * Common superclass for readers producing {@link PatchContext}.
+ *
+ * @author Robert Varga
+ */
+abstract class AbstractToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider<PatchContext> {
+
+    @Override
+    protected final PatchContext emptyBody(final InstanceIdentifierContext<?> path) {
+        return new PatchContext(path, null, null);
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonToPatchBodyReader.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonToPatchBodyReader.java
new file mode 100644 (file)
index 0000000..0dc8dd3
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers.patch;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.codecs.StringModuleInstanceIdentifierCodec;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+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.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON})
+public class JsonToPatchBodyReader extends AbstractToPatchBodyReader {
+    private static final Logger LOG = LoggerFactory.getLogger(JsonToPatchBodyReader.class);
+
+    private String patchId;
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @Override
+    protected PatchContext readBody(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException, WebApplicationException {
+        try {
+            return readFrom(path, entityStream);
+        } catch (final Exception e) {
+            throw propagateExceptionAs(e);
+        }
+    }
+
+    private PatchContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException {
+        final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream));
+        final List<PatchEntity> resultList = read(jsonReader, path);
+        jsonReader.close();
+
+        return new PatchContext(path, resultList, patchId);
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    public PatchContext readFrom(final String uriPath, final InputStream entityStream) throws
+            RestconfDocumentedException {
+        try {
+            return readFrom(
+                    ParserIdentifier.toInstanceIdentifier(uriPath, SchemaContextHandler.getActualSchemaContext(),
+                            DOMMountPointServiceHandler.getActualMountPointService()), entityStream);
+        } catch (final Exception e) {
+            propagateExceptionAs(e);
+            return null; // no-op
+        }
+    }
+
+    private static RuntimeException propagateExceptionAs(final Exception exception) throws RestconfDocumentedException {
+        if (exception instanceof RestconfDocumentedException) {
+            throw (RestconfDocumentedException)exception;
+        }
+
+        if (exception instanceof ResultAlreadySetException) {
+            LOG.debug("Error parsing json input:", exception);
+            throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+        }
+
+        throw new RestconfDocumentedException("Error parsing json input: " + exception.getMessage(), ErrorType.PROTOCOL,
+                ErrorTag.MALFORMED_MESSAGE, exception);
+    }
+
+    private List<PatchEntity> read(final JsonReader in, final InstanceIdentifierContext<?> path) throws IOException {
+        final List<PatchEntity> resultCollection = new ArrayList<>();
+        final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+                path.getSchemaContext());
+        final JsonToPatchBodyReader.PatchEdit edit = new JsonToPatchBodyReader.PatchEdit();
+
+        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:
+                    in.beginObject();
+                    break;
+                case END_DOCUMENT:
+                    break;
+                case NAME:
+                    parseByName(in.nextName(), edit, in, path, codec, resultCollection);
+                    break;
+                case END_OBJECT:
+                    in.endObject();
+                    break;
+                case END_ARRAY:
+                    in.endArray();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        return ImmutableList.copyOf(resultCollection);
+    }
+
+    /**
+     * Switch value of parsed JsonToken.NAME and read edit definition or patch id.
+     *
+     * @param name value of token
+     * @param edit PatchEdit instance
+     * @param in JsonReader reader
+     * @param path InstanceIdentifierContext context
+     * @param codec Draft11StringModuleInstanceIdentifierCodec codec
+     * @param resultCollection collection of parsed edits
+     * @throws IOException if operation fails
+     */
+    private void parseByName(@Nonnull final String name, @Nonnull final PatchEdit edit,
+                             @Nonnull final JsonReader in, @Nonnull final InstanceIdentifierContext<?> path,
+                             @Nonnull final StringModuleInstanceIdentifierCodec codec,
+                             @Nonnull final List<PatchEntity> resultCollection) throws IOException {
+        switch (name) {
+            case "edit":
+                if (in.peek() == JsonToken.BEGIN_ARRAY) {
+                    in.beginArray();
+
+                    while (in.hasNext()) {
+                        readEditDefinition(edit, in, path, codec);
+                        resultCollection.add(prepareEditOperation(edit));
+                        edit.clear();
+                    }
+
+                    in.endArray();
+                } else {
+                    readEditDefinition(edit, in, path, codec);
+                    resultCollection.add(prepareEditOperation(edit));
+                    edit.clear();
+                }
+
+                break;
+            case "patch-id":
+                this.patchId = in.nextString();
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Read one patch edit object from Json input.
+     *
+     * @param edit PatchEdit instance to be filled with read data
+     * @param in JsonReader reader
+     * @param path InstanceIdentifierContext path context
+     * @param codec Draft11StringModuleInstanceIdentifierCodec codec
+     * @throws IOException if operation fails
+     */
+    private void readEditDefinition(@Nonnull final PatchEdit edit, @Nonnull final JsonReader in,
+                                    @Nonnull final InstanceIdentifierContext<?> path,
+                                    @Nonnull final StringModuleInstanceIdentifierCodec codec) throws IOException {
+        String deferredValue = null;
+        in.beginObject();
+
+        while (in.hasNext()) {
+            final String editDefinition = in.nextName();
+            switch (editDefinition) {
+                case "edit-id":
+                    edit.setId(in.nextString());
+                    break;
+                case "operation":
+                    edit.setOperation(PatchEditOperation.valueOf(in.nextString().toUpperCase()));
+                    break;
+                case "target":
+                    // target can be specified completely in request URI
+                    final String target = in.nextString();
+                    if (target.equals("/")) {
+                        edit.setTarget(path.getInstanceIdentifier());
+                        edit.setTargetSchemaNode(path.getSchemaContext());
+                    } else {
+                        edit.setTarget(codec.deserialize(codec.serialize(path.getInstanceIdentifier()).concat(target)));
+                        edit.setTargetSchemaNode(SchemaContextUtil.findDataSchemaNode(path.getSchemaContext(),
+                                codec.getDataContextTree().getChild(edit.getTarget()).getDataSchemaNode().getPath()
+                                        .getParent()));
+                    }
+
+                    break;
+                case "value":
+                    Preconditions.checkArgument(edit.getData() == null && deferredValue == null,
+                            "Multiple value entries found");
+
+                    if (edit.getTargetSchemaNode() == null) {
+                        final StringBuilder sb = new StringBuilder();
+
+                        // save data defined in value node for next (later) processing, because target needs to be read
+                        // always first and there is no ordering in Json input
+                        readValueNode(sb, in);
+                        deferredValue = sb.toString();
+                    } else {
+                        // We have a target schema node, reuse this reader without buffering the value.
+                        edit.setData(readEditData(in, edit.getTargetSchemaNode(), path));
+                    }
+                    break;
+                default:
+                    // FIXME: this does not look right, as it can wreck our logic
+                    break;
+            }
+        }
+
+        in.endObject();
+
+        if (deferredValue != null) {
+            // read saved data to normalized node when target schema is already known
+            edit.setData(readEditData(new JsonReader(new StringReader(deferredValue)), edit.getTargetSchemaNode(),
+                path));
+        }
+    }
+
+    /**
+     * Parse data defined in value node and saves it to buffer.
+     * @param sb Buffer to read value node
+     * @param in JsonReader reader
+     * @throws IOException if operation fails
+     */
+    private void readValueNode(@Nonnull final StringBuilder sb, @Nonnull final JsonReader in) throws IOException {
+        in.beginObject();
+
+        sb.append("{\"").append(in.nextName()).append("\":");
+
+        switch (in.peek()) {
+            case BEGIN_ARRAY:
+                in.beginArray();
+                sb.append('[');
+
+                while (in.hasNext()) {
+                    if (in.peek() == JsonToken.STRING) {
+                        sb.append('"').append(in.nextString()).append('"');
+                    } else {
+                        readValueObject(sb, in);
+                    }
+                    if (in.peek() != JsonToken.END_ARRAY) {
+                        sb.append(',');
+                    }
+                }
+
+                in.endArray();
+                sb.append(']');
+                break;
+            default:
+                readValueObject(sb, in);
+                break;
+        }
+
+        in.endObject();
+        sb.append('}');
+    }
+
+    /**
+     * Parse one value object of data and saves it to buffer.
+     * @param sb Buffer to read value object
+     * @param in JsonReader reader
+     * @throws IOException if operation fails
+     */
+    private void readValueObject(@Nonnull final StringBuilder sb, @Nonnull final JsonReader in) throws IOException {
+        // read simple leaf value
+        if (in.peek() == JsonToken.STRING) {
+            sb.append('"').append(in.nextString()).append('"');
+            return;
+        }
+
+        in.beginObject();
+        sb.append('{');
+
+        while (in.hasNext()) {
+            sb.append('"').append(in.nextName()).append("\":");
+
+            switch (in.peek()) {
+                case STRING:
+                    sb.append('"').append(in.nextString()).append('"');
+                    break;
+                case BEGIN_ARRAY:
+                    in.beginArray();
+                    sb.append('[');
+
+                    while (in.hasNext()) {
+                        if (in.peek() == JsonToken.STRING) {
+                            sb.append('"').append(in.nextString()).append('"');
+                        } else {
+                            readValueObject(sb, in);
+                        }
+
+                        if (in.peek() != JsonToken.END_ARRAY) {
+                            sb.append(',');
+                        }
+                    }
+
+                    in.endArray();
+                    sb.append(']');
+                    break;
+                default:
+                    readValueObject(sb, in);
+            }
+
+            if (in.peek() != JsonToken.END_OBJECT) {
+                sb.append(',');
+            }
+        }
+
+        in.endObject();
+        sb.append('}');
+    }
+
+    /**
+     * Read patch edit data defined in value node to NormalizedNode.
+     * @param in reader JsonReader reader
+     * @return NormalizedNode representing data
+     */
+    private static NormalizedNode<?, ?> readEditData(@Nonnull final JsonReader in,
+            @Nonnull final SchemaNode targetSchemaNode, @Nonnull final InstanceIdentifierContext<?> path) {
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+        JsonParserStream.create(writer, path.getSchemaContext(), targetSchemaNode).parse(in);
+
+        return resultHolder.getResult();
+    }
+
+    /**
+     * Prepare PatchEntity from PatchEdit instance when it satisfies conditions, otherwise throws exception.
+     * @param edit Instance of PatchEdit
+     * @return PatchEntity Patch entity
+     */
+    private static PatchEntity prepareEditOperation(@Nonnull final PatchEdit edit) {
+        if (edit.getOperation() != null && edit.getTargetSchemaNode() != null
+                && checkDataPresence(edit.getOperation(), edit.getData() != null)) {
+            if (!edit.getOperation().isWithValue()) {
+                return new PatchEntity(edit.getId(), edit.getOperation(), edit.getTarget());
+            }
+
+            // for lists allow to manipulate with list items through their parent
+            final YangInstanceIdentifier targetNode;
+            if (edit.getTarget().getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+                targetNode = edit.getTarget().getParent();
+            } else {
+                targetNode = edit.getTarget();
+            }
+
+            return new PatchEntity(edit.getId(), edit.getOperation(), targetNode, edit.getData());
+        }
+
+        throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+    }
+
+    /**
+     * Check if data is present when operation requires it and not present when operation data is not allowed.
+     * @param operation Name of operation
+     * @param hasData Data in edit are present/not present
+     * @return true if data is present when operation requires it or if there are no data when operation does not
+     *     allow it, false otherwise
+     */
+    private static boolean checkDataPresence(@Nonnull final PatchEditOperation operation, final boolean hasData) {
+        return operation.isWithValue() == hasData;
+    }
+
+    /**
+     * Helper class representing one patch edit.
+     */
+    private static final class PatchEdit {
+        private String id;
+        private PatchEditOperation operation;
+        private YangInstanceIdentifier target;
+        private SchemaNode targetSchemaNode;
+        private NormalizedNode<?, ?> data;
+
+        String getId() {
+            return id;
+        }
+
+        void setId(final String id) {
+            this.id = Preconditions.checkNotNull(id);
+        }
+
+        PatchEditOperation getOperation() {
+            return operation;
+        }
+
+        void setOperation(final PatchEditOperation operation) {
+            this.operation = Preconditions.checkNotNull(operation);
+        }
+
+        YangInstanceIdentifier getTarget() {
+            return target;
+        }
+
+        void setTarget(final YangInstanceIdentifier target) {
+            this.target = Preconditions.checkNotNull(target);
+        }
+
+        SchemaNode getTargetSchemaNode() {
+            return targetSchemaNode;
+        }
+
+        void setTargetSchemaNode(final SchemaNode targetSchemaNode) {
+            this.targetSchemaNode = Preconditions.checkNotNull(targetSchemaNode);
+        }
+
+        NormalizedNode<?, ?> getData() {
+            return data;
+        }
+
+        void setData(final NormalizedNode<?, ?> data) {
+            this.data = Preconditions.checkNotNull(data);
+        }
+
+        void clear() {
+            id = null;
+            operation = null;
+            target = null;
+            targetSchemaNode = null;
+            data = null;
+        }
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/PatchJsonBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/PatchJsonBodyWriter.java
new file mode 100644 (file)
index 0000000..805b339
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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.restconf.nb.rfc8040.jersey.providers.patch;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+
+
+@Provider
+@Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.JSON })
+public class PatchJsonBodyWriter implements MessageBodyWriter<PatchStatusContext> {
+
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
+        return type.equals(PatchStatusContext.class);
+    }
+
+    @Override
+    public long getSize(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+            throws IOException, WebApplicationException {
+
+        final JsonWriter jsonWriter = createJsonWriter(entityStream);
+        jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status");
+        jsonWriter.beginObject();
+        jsonWriter.name("patch-id").value(patchStatusContext.getPatchId());
+        if (patchStatusContext.isOk()) {
+            reportSuccess(jsonWriter);
+        } else {
+            if (patchStatusContext.getGlobalErrors() != null) {
+                reportErrors(patchStatusContext.getGlobalErrors(), jsonWriter);
+            }
+
+            jsonWriter.name("edit-status");
+            jsonWriter.beginObject();
+            jsonWriter.name("edit");
+            jsonWriter.beginArray();
+            for (final PatchStatusEntity patchStatusEntity : patchStatusContext.getEditCollection()) {
+                jsonWriter.beginObject();
+                jsonWriter.name("edit-id").value(patchStatusEntity.getEditId());
+                if (patchStatusEntity.getEditErrors() != null) {
+                    reportErrors(patchStatusEntity.getEditErrors(), jsonWriter);
+                } else {
+                    if (patchStatusEntity.isOk()) {
+                        reportSuccess(jsonWriter);
+                    }
+                }
+                jsonWriter.endObject();
+            }
+            jsonWriter.endArray();
+            jsonWriter.endObject();
+        }
+        jsonWriter.endObject();
+        jsonWriter.endObject();
+        jsonWriter.flush();
+    }
+
+    private static void reportSuccess(final JsonWriter jsonWriter) throws IOException {
+        jsonWriter.name("ok").beginArray().nullValue().endArray();
+    }
+
+    private static void reportErrors(final List<RestconfError> errors, final JsonWriter jsonWriter) throws IOException {
+        jsonWriter.name("errors");
+        jsonWriter.beginObject();
+        jsonWriter.name("error");
+        jsonWriter.beginArray();
+
+        for (final RestconfError restconfError : errors) {
+            jsonWriter.beginObject();
+            jsonWriter.name("error-type").value(restconfError.getErrorType().getErrorTypeTag());
+            jsonWriter.name("error-tag").value(restconfError.getErrorTag().getTagValue());
+
+            // optional node
+            if (restconfError.getErrorPath() != null) {
+                jsonWriter.name("error-path").value(restconfError.getErrorPath().toString());
+            }
+
+            // optional node
+            if (restconfError.getErrorMessage() != null) {
+                jsonWriter.name("error-message").value(restconfError.getErrorMessage());
+            }
+
+            // optional node
+            if (restconfError.getErrorInfo() != null) {
+                jsonWriter.name("error-info").value(restconfError.getErrorInfo());
+            }
+
+            jsonWriter.endObject();
+        }
+
+        jsonWriter.endArray();
+        jsonWriter.endObject();
+    }
+
+    private static JsonWriter createJsonWriter(final OutputStream entityStream) {
+        return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8));
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/PatchXmlBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/PatchXmlBodyWriter.java
new file mode 100644 (file)
index 0000000..e80dd14
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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.restconf.nb.rfc8040.jersey.providers.patch;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+
+@Provider
+@Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.XML })
+public class PatchXmlBodyWriter implements MessageBodyWriter<PatchStatusContext> {
+
+    private static final XMLOutputFactory XML_FACTORY;
+
+    static {
+        XML_FACTORY = XMLOutputFactory.newFactory();
+        XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+    }
+
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
+        return type.equals(PatchStatusContext.class);
+    }
+
+    @Override
+    public long getSize(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+            throws IOException, WebApplicationException {
+
+        try {
+            final XMLStreamWriter xmlWriter =
+                    XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
+            writeDocument(xmlWriter, patchStatusContext);
+        } catch (final XMLStreamException e) {
+            throw new IllegalStateException(e);
+        } catch (final FactoryConfigurationError e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private static void writeDocument(final XMLStreamWriter writer, final PatchStatusContext context)
+            throws XMLStreamException, IOException {
+        writer.writeStartElement("", "yang-patch-status", "urn:ietf:params:xml:ns:yang:ietf-yang-patch");
+        writer.writeStartElement("patch-id");
+        writer.writeCharacters(context.getPatchId());
+        writer.writeEndElement();
+
+        if (context.isOk()) {
+            writer.writeEmptyElement("ok");
+        } else {
+            if (context.getGlobalErrors() != null) {
+                reportErrors(context.getGlobalErrors(), writer);
+            }
+            writer.writeStartElement("edit-status");
+            for (final PatchStatusEntity patchStatusEntity : context.getEditCollection()) {
+                writer.writeStartElement("edit");
+                writer.writeStartElement("edit-id");
+                writer.writeCharacters(patchStatusEntity.getEditId());
+                writer.writeEndElement();
+                if (patchStatusEntity.getEditErrors() != null) {
+                    reportErrors(patchStatusEntity.getEditErrors(), writer);
+                } else {
+                    if (patchStatusEntity.isOk()) {
+                        writer.writeEmptyElement("ok");
+                    }
+                }
+                writer.writeEndElement();
+            }
+            writer.writeEndElement();
+
+        }
+        writer.writeEndElement();
+
+        writer.flush();
+    }
+
+    private static void reportErrors(final List<RestconfError> errors, final XMLStreamWriter writer)
+            throws IOException, XMLStreamException {
+        writer.writeStartElement("errors");
+
+        for (final RestconfError restconfError : errors) {
+            writer.writeStartElement("error-type");
+            writer.writeCharacters(restconfError.getErrorType().getErrorTypeTag());
+            writer.writeEndElement();
+
+            writer.writeStartElement("error-tag");
+            writer.writeCharacters(restconfError.getErrorTag().getTagValue());
+            writer.writeEndElement();
+
+            // optional node
+            if (restconfError.getErrorPath() != null) {
+                writer.writeStartElement("error-path");
+                writer.writeCharacters(restconfError.getErrorPath().toString());
+                writer.writeEndElement();
+            }
+
+            // optional node
+            if (restconfError.getErrorMessage() != null) {
+                writer.writeStartElement("error-message");
+                writer.writeCharacters(restconfError.getErrorMessage());
+                writer.writeEndElement();
+            }
+
+            // optional node
+            if (restconfError.getErrorInfo() != null) {
+                writer.writeStartElement("error-info");
+                writer.writeCharacters(restconfError.getErrorInfo());
+                writer.writeEndElement();
+            }
+        }
+
+        writer.writeEndElement();
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlToPatchBodyReader.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlToPatchBodyReader.java
new file mode 100644 (file)
index 0000000..62cef20
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers.patch;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.codecs.StringModuleInstanceIdentifierCodec;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+@Provider
+@Consumes({Rfc8040.MediaTypes.PATCH + RestconfConstants.XML})
+public class XmlToPatchBodyReader extends AbstractToPatchBodyReader {
+    private static final Logger LOG = LoggerFactory.getLogger(XmlToPatchBodyReader.class);
+    private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @Override
+    protected PatchContext readBody(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException, WebApplicationException {
+        try {
+            final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
+            return parse(path, doc);
+        } catch (final RestconfDocumentedException e) {
+            throw e;
+        } catch (final Exception e) {
+            LOG.debug("Error parsing xml input", e);
+
+            throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+                    ErrorTag.MALFORMED_MESSAGE);
+        }
+    }
+
+    private static PatchContext parse(final InstanceIdentifierContext<?> pathContext, final Document doc)
+            throws XMLStreamException, IOException, ParserConfigurationException, SAXException, URISyntaxException {
+        final List<PatchEntity> resultCollection = new ArrayList<>();
+        final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
+        final NodeList editNodes = doc.getElementsByTagName("edit");
+
+        for (int i = 0; i < editNodes.getLength(); i++) {
+            DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode();
+            final Element element = (Element) editNodes.item(i);
+            final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue();
+            final PatchEditOperation oper = PatchEditOperation.valueOf(operation.toUpperCase());
+            final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue();
+            final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue();
+            final List<Element> values = readValueNodes(element, oper);
+            final Element firstValueElement = values != null ? values.get(0) : null;
+
+            // get namespace according to schema node from path context or value
+            final String namespace = firstValueElement == null
+                    ? schemaNode.getQName().getNamespace().toString() : firstValueElement.getNamespaceURI();
+
+            // find module according to namespace
+            final Module module = pathContext.getSchemaContext().findModuleByNamespace(
+                    URI.create(namespace)).iterator().next();
+
+            // initialize codec + set default prefix derived from module name
+            final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+                    pathContext.getSchemaContext(), module.getName());
+
+            // find complete path to target and target schema node
+            // target can be also empty (only slash)
+            YangInstanceIdentifier targetII;
+            final SchemaNode targetNode;
+            if (target.equals("/")) {
+                targetII = pathContext.getInstanceIdentifier();
+                targetNode = pathContext.getSchemaContext();
+            } else {
+                targetII = codec.deserialize(codec.serialize(pathContext.getInstanceIdentifier())
+                        .concat(prepareNonCondXpath(schemaNode, target.replaceFirst("/", ""), firstValueElement,
+                                namespace, module.getQNameModule().getFormattedRevision())));
+
+                targetNode = SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(),
+                        codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath().getParent());
+
+                // move schema node
+                schemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(),
+                        codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath());
+            }
+
+            if (targetNode == null) {
+                LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode());
+                throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
+                        ErrorTag.MALFORMED_MESSAGE);
+            }
+
+            if (oper.isWithValue()) {
+                final NormalizedNode<?, ?> parsed;
+                if (schemaNode instanceof  ContainerSchemaNode || schemaNode instanceof ListSchemaNode) {
+                    final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+                    final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+                    final XmlParserStream xmlParser = XmlParserStream.create(writer, pathContext.getSchemaContext(),
+                            schemaNode);
+                    xmlParser.traverse(new DOMSource(firstValueElement));
+                    parsed = resultHolder.getResult();
+                } else {
+                    parsed = null;
+                }
+
+                // for lists allow to manipulate with list items through their parent
+                if (targetII.getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+                    targetII = targetII.getParent();
+                }
+
+                resultCollection.add(new PatchEntity(editId, oper, targetII, parsed));
+            } else {
+                resultCollection.add(new PatchEntity(editId, oper, targetII));
+            }
+        }
+
+        return new PatchContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
+    }
+
+    /**
+     * Read value nodes.
+     *
+     * @param element Element of current edit operation
+     * @param operation Name of current operation
+     * @return List of value elements
+     */
+    private static List<Element> readValueNodes(@Nonnull final Element element,
+            @Nonnull final PatchEditOperation operation) {
+        final Node valueNode = element.getElementsByTagName("value").item(0);
+
+        if (operation.isWithValue() && valueNode == null) {
+            throw new RestconfDocumentedException("Error parsing input",
+                    ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+
+        if (!operation.isWithValue() && valueNode != null) {
+            throw new RestconfDocumentedException("Error parsing input",
+                    ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+
+        if (valueNode == null) {
+            return null;
+        }
+
+        final List<Element> result = new ArrayList<>();
+        final NodeList childNodes = valueNode.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            if (childNodes.item(i) instanceof Element) {
+                result.add((Element) childNodes.item(i));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Prepare non-conditional XPath suitable for deserialization with {@link StringModuleInstanceIdentifierCodec}.
+     *
+     * @param schemaNode Top schema node
+     * @param target Edit operation target
+     * @param value Element with value
+     * @param namespace Module namespace
+     * @param revision Module revision
+     * @return Non-conditional XPath
+     */
+    private static String prepareNonCondXpath(@Nonnull final DataSchemaNode schemaNode, @Nonnull final String target,
+            @Nonnull final Element value, @Nonnull final String namespace, @Nonnull final String revision) {
+        final Iterator<String> args = SLASH_SPLITTER.split(target.substring(target.indexOf(':') + 1)).iterator();
+
+        final StringBuilder nonCondXpath = new StringBuilder();
+        SchemaNode childNode = schemaNode;
+
+        while (args.hasNext()) {
+            final String s = args.next();
+            nonCondXpath.append("/");
+            nonCondXpath.append(s);
+            childNode = ((DataNodeContainer) childNode).getDataChildByName(QName.create(namespace, revision, s));
+
+            if (childNode instanceof ListSchemaNode && args.hasNext()) {
+                appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), args);
+            }
+        }
+
+        if (childNode instanceof ListSchemaNode && value != null) {
+            final Iterator<String> keyValues = readKeyValues(value,
+                    ((ListSchemaNode) childNode).getKeyDefinition().iterator());
+            appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), keyValues);
+        }
+
+        return nonCondXpath.toString();
+    }
+
+    /**
+     * Read value for every list key.
+     *
+     * @param value Value element
+     * @param keys Iterator of list keys names
+     * @return Iterator of list keys values
+     */
+    private static Iterator<String> readKeyValues(@Nonnull final Element value, @Nonnull final Iterator<QName> keys) {
+        final List<String> result = new ArrayList<>();
+
+        while (keys.hasNext()) {
+            result.add(value.getElementsByTagName(keys.next().getLocalName()).item(0).getFirstChild().getNodeValue());
+        }
+
+        return result.iterator();
+    }
+
+    /**
+     * Append key name - key value pairs for every list key to {@code nonCondXpath}.
+     *
+     * @param nonCondXpath Builder for creating non-conditional XPath
+     * @param keyNames Iterator of list keys names
+     * @param keyValues Iterator of list keys values
+     */
+    private static void appendKeys(@Nonnull final StringBuilder nonCondXpath, @Nonnull final Iterator<QName> keyNames,
+                            @Nonnull final Iterator<String> keyValues) {
+        while (keyNames.hasNext()) {
+            nonCondXpath.append('[');
+            nonCondXpath.append(keyNames.next().getLocalName());
+            nonCondXpath.append("='");
+            nonCondXpath.append(keyValues.next());
+            nonCondXpath.append("']");
+        }
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/schema/SchemaExportContentYangBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/schema/SchemaExportContentYangBodyWriter.java
new file mode 100644 (file)
index 0000000..96bc26b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014 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.restconf.nb.rfc8040.jersey.providers.schema;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+
+@Provider
+@Produces({ Rfc8040.MediaTypes.YANG })
+public class SchemaExportContentYangBodyWriter implements MessageBodyWriter<SchemaExportContext> {
+
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+            final MediaType mediaType) {
+        return type.equals(SchemaExportContext.class);
+    }
+
+    @Override
+    public long getSize(final SchemaExportContext context, final Class<?> type, final Type genericType,
+            final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final SchemaExportContext context, final Class<?> type, final Type genericType,
+            final Annotation[] annotations, final MediaType mediaType,
+            final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException,
+            WebApplicationException {
+        final PrintWriter writer = new PrintWriter(entityStream);
+        writer.write(context.getModule().getSource());
+
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/schema/SchemaExportContentYinBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/schema/SchemaExportContentYinBodyWriter.java
new file mode 100644 (file)
index 0000000..cad1b7e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014 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.restconf.nb.rfc8040.jersey.providers.schema;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.XMLStreamException;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.model.export.YinExportUtils;
+
+@Provider
+@Produces({ Rfc8040.MediaTypes.YIN + RestconfConstants.XML })
+public class SchemaExportContentYinBodyWriter implements MessageBodyWriter<SchemaExportContext> {
+
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+            final MediaType mediaType) {
+        return type.equals(SchemaExportContext.class);
+    }
+
+    @Override
+    public long getSize(final SchemaExportContext context, final Class<?> type, final Type genericType,
+            final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final SchemaExportContext context, final Class<?> type, final Type genericType,
+            final Annotation[] annotations, final MediaType mediaType,
+            final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException,
+            WebApplicationException {
+        try {
+            YinExportUtils.writeModuleToOutputStream(context.getSchemaContext(), context.getModule(), entityStream);
+        } catch (final XMLStreamException e) {
+            throw new IllegalStateException(e);
+        }
+
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractIdentifierAwareJaxRsProvider.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractIdentifierAwareJaxRsProvider.java
new file mode 100644 (file)
index 0000000..42c2d0b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers.spi;
+
+import com.google.common.base.Optional;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.MessageBodyReader;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.nb.rfc8040.RestConnectorProvider;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+
+public abstract class AbstractIdentifierAwareJaxRsProvider<T> implements MessageBodyReader<T> {
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private Request request;
+
+    @Override
+    public final boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+            final MediaType mediaType) {
+        return true;
+    }
+
+    @Override
+    public final T readFrom(final Class<T> type, final Type genericType,
+            final Annotation[] annotations, final MediaType mediaType,
+            final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException,
+            WebApplicationException {
+        final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
+        if (entityStream.available() < 1) {
+            return emptyBody(path);
+        }
+
+        return readBody(path, entityStream);
+    }
+
+    /**
+     * Create a type corresponding to an empty body.
+     *
+     * @param path Request path
+     * @return empty body type
+     */
+    protected abstract T emptyBody(InstanceIdentifierContext<?> path);
+
+    protected abstract T readBody(InstanceIdentifierContext<?> path, InputStream entityStream)
+            throws IOException, WebApplicationException;
+
+
+    private String getIdentifier() {
+        return this.uriInfo.getPathParameters(false).getFirst(RestconfConstants.IDENTIFIER);
+    }
+
+    private InstanceIdentifierContext<?> getInstanceIdentifierContext() {
+        return ParserIdentifier.toInstanceIdentifier(
+                getIdentifier(),
+                SchemaContextHandler.getActualSchemaContext(),
+                Optional.of(RestConnectorProvider.getMountPointService()));
+    }
+
+    protected UriInfo getUriInfo() {
+        return this.uriInfo;
+    }
+
+    protected boolean isPost() {
+        return HttpMethod.POST.equals(this.request.getMethod());
+    }
+
+    void setUriInfo(final UriInfo uriInfo) {
+        this.uriInfo = uriInfo;
+    }
+
+    void setRequest(final Request request) {
+        this.request = request;
+    }
+}
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractNormalizedNodeBodyReader.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/spi/AbstractNormalizedNodeBodyReader.java
new file mode 100644 (file)
index 0000000..6114754
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.restconf.nb.rfc8040.jersey.providers.spi;
+
+import com.google.common.annotations.Beta;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+
+/**
+ * Common superclass for readers producing {@link NormalizedNodeContext}.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public abstract class AbstractNormalizedNodeBodyReader
+        extends AbstractIdentifierAwareJaxRsProvider<NormalizedNodeContext> {
+
+    public final void injectParams(final UriInfo uriInfo, final Request request) {
+        setUriInfo(uriInfo);
+        setRequest(request);
+    }
+
+    @Override
+    protected final NormalizedNodeContext emptyBody(final InstanceIdentifierContext<?> path) {
+        return new NormalizedNodeContext(path, null);
+    }
+}