summaryrefslogtreecommitdiffstats
path: root/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest
diff options
context:
space:
mode:
Diffstat (limited to 'netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest')
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java67
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java17
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java15
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java18
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java435
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java9
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java63
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java305
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java174
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java486
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java68
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java200
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java181
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java130
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java143
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java72
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java128
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java51
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java430
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java62
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java29
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java49
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java200
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java313
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java8
25 files changed, 3653 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java
new file mode 100644
index 0000000..67c8729
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java
@@ -0,0 +1,67 @@
+/*
+ * 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.netconf.sal.rest.api;
+
+import org.opendaylight.yangtools.yang.common.QName;
+
+/**
+ * Base Draft for Restconf project.
+ *
+ * @deprecated Do not use old implementation of restconf draft. It will be replaced by Rfc8040.
+ */
+@Deprecated
+public class Draft02 {
+ public interface MediaTypes {
+ String API = "application/yang.api";
+ String DATASTORE = "application/yang.datastore";
+ String DATA = "application/yang.data";
+ String OPERATION = "application/yang.operation";
+ String PATCH = "application/yang.patch";
+ String PATCH_STATUS = "application/yang.patch-status";
+ String STREAM = "application/yang.stream";
+ }
+
+ public interface RestConfModule {
+ String REVISION = "2013-10-19";
+
+ String NAME = "ietf-restconf";
+
+ String NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf";
+
+ String RESTCONF_GROUPING_SCHEMA_NODE = "restconf";
+
+ String RESTCONF_CONTAINER_SCHEMA_NODE = "restconf";
+
+ String MODULES_CONTAINER_SCHEMA_NODE = "modules";
+
+ String MODULE_LIST_SCHEMA_NODE = "module";
+
+ String STREAMS_CONTAINER_SCHEMA_NODE = "streams";
+
+ String STREAM_LIST_SCHEMA_NODE = "stream";
+
+ String ERROR_LIST_SCHEMA_NODE = "error";
+
+ QName IETF_RESTCONF_QNAME = QName.create(Draft02.RestConfModule.NAMESPACE, Draft02.RestConfModule.REVISION,
+ Draft02.RestConfModule.NAME);
+
+ QName ERRORS_QNAME = QName.create(IETF_RESTCONF_QNAME, "errors");
+
+ QName ERROR_LIST_QNAME = QName.create(IETF_RESTCONF_QNAME, ERROR_LIST_SCHEMA_NODE);
+
+ QName ERROR_TYPE_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-type");
+
+ QName ERROR_TAG_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-tag");
+
+ QName ERROR_APP_TAG_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-app-tag");
+
+ QName ERROR_MESSAGE_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-message");
+
+ QName ERROR_INFO_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-info");
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java
new file mode 100644
index 0000000..3200958
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.api;
+
+/*
+ * This is a simple dummy interface to allow us to create instances of RestconfProvider
+ * via the config subsystem.
+ */
+public interface RestConnector {
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java
new file mode 100644
index 0000000..6a4ba93
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.api;
+
+public interface RestconfConstants {
+
+ String IDENTIFIER = "identifier";
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java
new file mode 100644
index 0000000..eb160bd
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.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/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java
new file mode 100644
index 0000000..0d0d2ab
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.Encoded;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.Patch;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+
+/**
+ * The URI hierarchy for the RESTCONF resources consists of an entry point
+ * container, 4 top-level resources, and 1 field.
+ * <ul>
+ * <li><b>/restconf</b> - {@link #getRoot()}
+ * <ul>
+ * <li><b>/config</b> - {@link #readConfigurationData(String, UriInfo)}
+ * {@link #updateConfigurationData(String, NormalizedNodeContext, UriInfo)}
+ * {@link #createConfigurationData(NormalizedNodeContext, UriInfo)}
+ * {@link #createConfigurationData(String, NormalizedNodeContext, UriInfo)}
+ * {@link #deleteConfigurationData(String)}
+ * <li><b>/operational</b> - {@link #readOperationalData(String, UriInfo)}
+ * <li>/modules - {@link #getModules(UriInfo)}
+ * <ul>
+ * <li>/module
+ * </ul>
+ * <li><b>/operations</b> -
+ * {@link #invokeRpc(String, NormalizedNodeContext, UriInfo)}
+ * {@link #invokeRpc(String, NormalizedNodeContext, UriInfo)}
+ * <li>/version (field)
+ * </ul>
+ * </ul>
+ */
+@Path("/")
+public interface RestconfService {
+
+ String XML = "+xml";
+ String JSON = "+json";
+
+ @GET
+ Object getRoot();
+
+ /**
+ * Get all modules supported by controller.
+ *
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/modules")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getModules(@Context UriInfo uriInfo);
+
+ /**
+ * Get all modules supported by mount point.
+ *
+ * @param identifier
+ * mount point identifier
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/modules/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getModules(@PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * Get module.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/modules/module/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getModule(@PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * List of rpc or action operations supported by the server.
+ *
+ * @return A JSON document string
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfOperationsService#getOperations(UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operations")
+ @Produces({ Draft02.MediaTypes.API + JSON, MediaType.APPLICATION_JSON })
+ String getOperationsJSON();
+
+ /**
+ * List of rpc or action operations supported by the server.
+ *
+ * @return A XML document string
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfOperationsService#getOperations(UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operations")
+ @Produces({ Draft02.MediaTypes.API + XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ String getOperationsXML();
+
+ /**
+ * Valid for mount points. List of operations supported by the server.
+ *
+ * @param identifier
+ * path parameter
+ * @param uriInfo
+ * URI information
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfOperationsService#getOperations(String, UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operations/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getOperations(@PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * Invoke RPC operation.
+ *
+ * @param identifier
+ * module name and rpc identifier string for the desired operation
+ * @param payload
+ * {@link NormalizedNodeContext} - the body of the operation
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfInvokeOperationsService#invokeRpc(String, NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @POST
+ @Path("/operations/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.OPERATION + JSON,
+ Draft02.MediaTypes.OPERATION + XML,
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ @Consumes({
+ Draft02.MediaTypes.OPERATION + JSON,
+ Draft02.MediaTypes.OPERATION + XML,
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext invokeRpc(@Encoded @PathParam("identifier") String identifier, NormalizedNodeContext payload,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Get target data resource from config data store.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/config/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext readConfigurationData(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Get target data resource from operational data store.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operational/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext readOperationalData(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Create or replace the target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @param payload
+ * data node for put to config DS
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#putData(String,
+ * NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @PUT
+ @Path("/config/{identifier:.+}")
+ @Consumes({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ Response updateConfigurationData(@Encoded @PathParam("identifier") String identifier,
+ NormalizedNodeContext payload, @Context UriInfo uriInfo);
+
+ /**
+ * Create a data resource in target.
+ *
+ * @param identifier
+ * path to target
+ * @param payload
+ * new data
+ * @param uriInfo
+ * URI info
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#postData(String,
+ * NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @POST
+ @Path("/config/{identifier:.+}")
+ @Consumes({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ Response createConfigurationData(@Encoded @PathParam("identifier") String identifier, NormalizedNodeContext payload,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Create a data resource.
+ *
+ * @param payload
+ * new data
+ * @param uriInfo
+ * URI info
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfDataService#postData(NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @POST
+ @Path("/config")
+ @Consumes({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ Response createConfigurationData(NormalizedNodeContext payload, @Context UriInfo uriInfo);
+
+ /**
+ * Delete the target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#deleteData(String)
+ */
+ @Deprecated
+ @DELETE
+ @Path("/config/{identifier:.+}")
+ Response deleteConfigurationData(@Encoded @PathParam("identifier") String identifier);
+
+ /**
+ * Subscribe to stream.
+ *
+ * @param identifier
+ * stream identifier
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfStreamsSubscriptionService#subscribeToStream(String, UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/streams/stream/{identifier:.+}")
+ NormalizedNodeContext subscribeToStream(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Get list of all streams.
+ *
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ **/
+ @Deprecated
+ @GET
+ @Path("/streams")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getAvailableStreams(@Context UriInfo uriInfo);
+
+ /**
+ * Ordered list of edits that are applied to the target datastore by the server.
+ *
+ * @param identifier
+ * path to target
+ * @param context
+ * edits
+ * @param uriInfo
+ * URI info
+ * @return {@link PatchStatusContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#patchData(String,
+ * PatchContext, UriInfo)
+ */
+ @Deprecated
+ @Patch
+ @Path("/config/{identifier:.+}")
+ @Consumes({
+ MediaTypes.PATCH + JSON,
+ MediaTypes.PATCH + XML
+ })
+ @Produces({
+ MediaTypes.PATCH_STATUS + JSON,
+ MediaTypes.PATCH_STATUS + XML
+ })
+ PatchStatusContext patchConfigurationData(@Encoded @PathParam("identifier") String identifier, PatchContext
+ context, @Context UriInfo uriInfo);
+
+ /**
+ * Ordered list of edits that are applied to the datastore by the server.
+ *
+ * @param context
+ * edits
+ * @param uriInfo
+ * URI info
+ * @return {@link PatchStatusContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#patchData(PatchContext,
+ * UriInfo)
+ */
+ @Deprecated
+ @Patch
+ @Path("/config")
+ @Consumes({
+ MediaTypes.PATCH + JSON,
+ MediaTypes.PATCH + XML
+ })
+ @Produces({
+ MediaTypes.PATCH_STATUS + JSON,
+ MediaTypes.PATCH_STATUS + XML
+ })
+ PatchStatusContext patchConfigurationData(PatchContext context, @Context UriInfo uriInfo);
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java
new file mode 100644
index 0000000..b765331
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java
@@ -0,0 +1,9 @@
+/*
+ * 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.netconf.sal.rest.api;
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java
new file mode 100644
index 0000000..76ffdb4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+
+/**
+ * JAX-RS Provider.
+ *
+ * @deprecated This class will be replaced by AbstractIdentifierAwareJaxRsProvider in restconf-nb-rfc8040
+ */
+@Deprecated
+public class AbstractIdentifierAwareJaxRsProvider {
+
+ private static final String POST = "POST";
+
+ @Context
+ private UriInfo uriInfo;
+
+ @Context
+ private Request request;
+
+ private final ControllerContext controllerContext;
+
+ protected AbstractIdentifierAwareJaxRsProvider(final ControllerContext controllerContext) {
+ this.controllerContext = controllerContext;
+ }
+
+ protected final String getIdentifier() {
+ return uriInfo.getPathParameters(false).getFirst(RestconfConstants.IDENTIFIER);
+ }
+
+ protected InstanceIdentifierContext getInstanceIdentifierContext() {
+ return controllerContext.toInstanceIdentifier(getIdentifier());
+ }
+
+ protected UriInfo getUriInfo() {
+ return uriInfo;
+ }
+
+ protected boolean isPost() {
+ return POST.equals(request.getMethod());
+ }
+
+ protected ControllerContext getControllerContext() {
+ return controllerContext;
+ }
+
+ Request getRequest() {
+ return request;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java
new file mode 100644
index 0000000..b9d7a7a
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
+
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.netconf.sal.rest.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.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+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.
+ *
+ * @deprecated This class will be replaced by ParameterAwareNormalizedNodeWriter in restconf-nb-rfc8040
+ */
+@Deprecated
+public class DepthAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
+ private final NormalizedNodeStreamWriter writer;
+ protected int currentDepth = 0;
+ protected final int maxDepth;
+
+ private DepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
+ this.writer = requireNonNull(writer);
+ this.maxDepth = maxDepth;
+ }
+
+ protected final NormalizedNodeStreamWriter getWriter() {
+ return writer;
+ }
+
+ /**
+ * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
+ *
+ * @param writer Back-end writer
+ * @return A new instance.
+ */
+ public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final int maxDepth) {
+ return forStreamWriter(writer, true, maxDepth);
+ }
+
+ /**
+ * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
+ * Unlike the simple {@link #forStreamWriter(NormalizedNodeStreamWriter, int)}
+ * 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.
+ * @return A new instance.
+ */
+ public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final boolean orderKeyLeaves, final int maxDepth) {
+ return orderKeyLeaves ? new OrderedDepthAwareNormalizedNodeWriter(writer, maxDepth)
+ : new DepthAwareNormalizedNodeWriter(writer, maxDepth);
+ }
+
+ /**
+ * Iterate over the provided {@link NormalizedNode} and emit write
+ * events to the encapsulated {@link NormalizedNodeStreamWriter}.
+ *
+ * @param node Node
+ * @return DepthAwareNormalizedNodeWriter
+ * @throws IOException when thrown from the backing writer.
+ */
+ @Override
+ public final DepthAwareNormalizedNodeWriter 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 (currentDepth < maxDepth) {
+ final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>) node;
+ writer.startLeafSetEntryNode(nodeAsLeafList.getIdentifier());
+ writer.scalarValue(nodeAsLeafList.body());
+ writer.endNode();
+ }
+ return true;
+ } else if (node instanceof LeafNode) {
+ final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
+ writer.startLeafNode(nodeAsLeaf.getIdentifier());
+ writer.scalarValue(nodeAsLeaf.body());
+ writer.endNode();
+ return true;
+ } else if (node instanceof AnyxmlNode) {
+ final AnyxmlNode<?> anyxmlNode = (AnyxmlNode<?>)node;
+ final Class<?> objectModel = anyxmlNode.bodyObjectModel();
+ if (writer.startAnyxmlNode(anyxmlNode.getIdentifier(), objectModel)) {
+ if (DOMSource.class.isAssignableFrom(objectModel)) {
+ writer.domSourceValue((DOMSource) anyxmlNode.body());
+ } else {
+ writer.scalarValue(anyxmlNode.body());
+ }
+ writer.endNode();
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Emit events for all children and then emit an endNode() event.
+ *
+ * @param children Child iterable
+ * @return True
+ * @throws IOException when the writer reports it
+ */
+ protected final boolean writeChildren(final Iterable<? extends NormalizedNode> children) throws IOException {
+ if (currentDepth < maxDepth) {
+ for (final NormalizedNode child : children) {
+ write(child);
+ }
+ }
+ writer.endNode();
+ return true;
+ }
+
+ protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
+ if (currentDepth < maxDepth) {
+ writeChildren(mapEntryNode.body());
+ } else if (currentDepth == maxDepth) {
+ writeOnlyKeys(mapEntryNode.getIdentifier().entrySet());
+ }
+ return true;
+ }
+
+ private void writeOnlyKeys(final Set<Entry<QName, Object>> entries) throws IOException {
+ for (final Entry<QName, Object> entry : entries) {
+ writer.startLeafNode(new NodeIdentifier(entry.getKey()));
+ writer.scalarValue(entry.getValue());
+ writer.endNode();
+ }
+ writer.endNode();
+ }
+
+ protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
+ writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.body()));
+ 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;
+ writer.startContainerNode(n.getIdentifier(), childSizeHint(n.body()));
+ currentDepth++;
+ processedAsCompositeNode = writeChildren(n.body());
+ currentDepth--;
+ } 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.body()));
+ currentDepth++;
+ processedAsCompositeNode = writeChildren(n.body());
+ currentDepth--;
+ } else if (node instanceof ChoiceNode) {
+ final ChoiceNode n = (ChoiceNode) node;
+ writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof AugmentationNode) {
+ final AugmentationNode n = (AugmentationNode) node;
+ writer.startAugmentationNode(n.getIdentifier());
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof UnkeyedListNode) {
+ final UnkeyedListNode n = (UnkeyedListNode) node;
+ writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof UserMapNode) {
+ final UserMapNode n = (UserMapNode) node;
+ writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof MapNode) {
+ final MapNode n = (MapNode) node;
+ writer.startMapNode(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof LeafSetNode) {
+ final LeafSetNode<?> n = (LeafSetNode<?>) node;
+ if (node instanceof UserLeafSetNode) {
+ writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.body()));
+ } else {
+ writer.startLeafSet(n.getIdentifier(), childSizeHint(n.body()));
+ }
+ currentDepth++;
+ processedAsCompositeNode = writeChildren(n.body());
+ currentDepth--;
+ }
+
+ return processedAsCompositeNode;
+ }
+
+ private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter {
+ private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class);
+
+ OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
+ super(writer, maxDepth);
+ }
+
+ @Override
+ protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
+ final NormalizedNodeStreamWriter writer = getWriter();
+ writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.body()));
+
+ final Set<QName> qnames = node.getIdentifier().keySet();
+ // Write out all the key children
+ for (final QName qname : qnames) {
+ final Optional<? extends NormalizedNode> child = node.findChildByArg(new NodeIdentifier(qname));
+ if (child.isPresent()) {
+ write(child.get());
+ } else {
+ LOG.info("No child for key element {} found", qname);
+ }
+ }
+
+ // Write all the rest
+ currentDepth++;
+ final boolean result = writeChildren(Iterables.filter(node.body(), input -> {
+ if (input instanceof AugmentationNode) {
+ return true;
+ }
+ if (!qnames.contains(input.getIdentifier().getNodeType())) {
+ return true;
+ }
+
+ LOG.debug("Skipping key child {}", input);
+ return false;
+ }));
+ currentDepth--;
+ return result;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java
new file mode 100644
index 0000000..aecea7a
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java
@@ -0,0 +1,174 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+import com.google.common.base.Throwables;
+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.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+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.JSONCodecFactorySupplier;
+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.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({
+ Draft02.MediaTypes.DATA + RestconfService.JSON,
+ Draft02.MediaTypes.OPERATION + RestconfService.JSON,
+ MediaType.APPLICATION_JSON
+})
+public class JsonNormalizedNodeBodyReader
+ extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader<NormalizedNodeContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JsonNormalizedNodeBodyReader.class);
+
+ public JsonNormalizedNodeBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readFrom(final Class<NormalizedNodeContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws
+ WebApplicationException {
+ try {
+ return readFrom(getInstanceIdentifierContext(), entityStream, isPost());
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public static NormalizedNodeContext readFrom(final String uriPath, final InputStream entityStream,
+ final boolean isPost, final ControllerContext controllerContext) throws RestconfDocumentedException {
+
+ try {
+ return readFrom(controllerContext.toInstanceIdentifier(uriPath), entityStream, isPost);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private static NormalizedNodeContext readFrom(final InstanceIdentifierContext path,
+ final InputStream entityStream, final boolean isPost)
+ throws IOException {
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ return new NormalizedNodeContext(path, null);
+ }
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ final Inference parentInference;
+ if (isPost && !(path.getSchemaNode() instanceof RpcDefinition)) {
+ parentInference = path.inference();
+ } else {
+ final var inference = path.inference();
+ if (!inference.statementPath().isEmpty()) {
+ final var stack = inference.toSchemaInferenceStack();
+ stack.exit();
+ parentInference = stack.toInference();
+ } else {
+ parentInference = inference;
+ }
+ }
+
+ final JsonParserStream jsonParser = JsonParserStream.create(writer,
+ JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(path.getSchemaContext()),
+ parentInference);
+ final JsonReader reader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get(),
+ StandardCharsets.UTF_8));
+ jsonParser.parse(reader);
+
+ NormalizedNode result = resultHolder.getResult();
+ final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
+ InstanceIdentifierContext newIIContext;
+
+ while (result instanceof AugmentationNode || result instanceof ChoiceNode) {
+ final Object childNode = ((DataContainerNode) result).body().iterator().next();
+ if (isPost) {
+ iiToDataList.add(result.getIdentifier());
+ }
+ result = (NormalizedNode) childNode;
+ }
+
+ if (isPost) {
+ if (result instanceof MapEntryNode) {
+ iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(result.getIdentifier().getNodeType()));
+ iiToDataList.add(result.getIdentifier());
+ } else {
+ iiToDataList.add(result.getIdentifier());
+ }
+ } else {
+ if (result instanceof MapNode) {
+ result = Iterables.getOnlyElement(((MapNode) result).body());
+ }
+ }
+
+ return new NormalizedNodeContext(path.withConcatenatedArgs(iiToDataList), result);
+ }
+
+ private static void propagateExceptionAs(final Exception exception) throws RestconfDocumentedException {
+ Throwables.throwIfInstanceOf(exception, RestconfDocumentedException.class);
+ LOG.debug("Error parsing json input", exception);
+
+ if (exception instanceof ResultAlreadySetException) {
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. "
+ + "Are you creating multiple resources/subresources in POST request?", exception);
+ }
+
+ RestconfDocumentedException.throwIfYangError(exception);
+ throw new RestconfDocumentedException("Error parsing input: " + exception.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, exception);
+ }
+}
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java
new file mode 100644
index 0000000..ce5a8c4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.base.Throwables;
+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.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+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.common.util.RestUtil;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+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.JSONCodecFactorySupplier;
+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.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Patch reader for JSON.
+ *
+ * @deprecated This class will be replaced by JsonToPatchBodyReader in restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON})
+public class JsonToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider
+ implements MessageBodyReader<PatchContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JsonToPatchBodyReader.class);
+
+ public JsonToPatchBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchContext readFrom(final Class<PatchContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+ throws WebApplicationException {
+ try {
+ return readFrom(getInstanceIdentifierContext(), entityStream);
+ } catch (final Exception e) {
+ throw propagateExceptionAs(e);
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public PatchContext readFrom(final String uriPath, final InputStream entityStream) throws
+ RestconfDocumentedException {
+ try {
+ return readFrom(getControllerContext().toInstanceIdentifier(uriPath), entityStream);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private PatchContext readFrom(final InstanceIdentifierContext path, final InputStream entityStream)
+ throws IOException {
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ return new PatchContext(path, null, null);
+ }
+
+ final JsonReader jsonReader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get(),
+ StandardCharsets.UTF_8));
+ AtomicReference<String> patchId = new AtomicReference<>();
+ final List<PatchEntity> resultList = read(jsonReader, path, patchId);
+ jsonReader.close();
+
+ return new PatchContext(path, resultList, patchId.get());
+ }
+
+ private static RuntimeException propagateExceptionAs(final Exception exception) throws RestconfDocumentedException {
+ Throwables.throwIfInstanceOf(exception, RestconfDocumentedException.class);
+ LOG.debug("Error parsing json input", exception);
+
+ if (exception instanceof ResultAlreadySetException) {
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+ }
+
+ RestconfDocumentedException.throwIfYangError(exception);
+ 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,
+ final AtomicReference<String> patchId) 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, patchId);
+ 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 StringModuleInstanceIdentifierCodec codec
+ * @param resultCollection collection of parsed edits
+ * @throws IOException if operation fails
+ */
+ private void parseByName(final @NonNull String name, final @NonNull PatchEdit edit,
+ final @NonNull JsonReader in, final @NonNull InstanceIdentifierContext path,
+ final @NonNull StringModuleInstanceIdentifierCodec codec,
+ final @NonNull List<PatchEntity> resultCollection,
+ final @NonNull AtomicReference<String> patchId) 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" :
+ patchId.set(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 StringModuleInstanceIdentifierCodec codec
+ * @throws IOException if operation fails
+ */
+ private void readEditDefinition(final @NonNull PatchEdit edit, final @NonNull JsonReader in,
+ final @NonNull InstanceIdentifierContext path,
+ final @NonNull StringModuleInstanceIdentifierCodec codec) throws IOException {
+ final StringBuilder value = new StringBuilder();
+ 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(Locale.ROOT)));
+ 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(SchemaInferenceStack.of(path.getSchemaContext()).toInference());
+ } else {
+ edit.setTarget(codec.deserialize(codec.serialize(path.getInstanceIdentifier()).concat(target)));
+
+ final var stack = codec.getDataContextTree().enterPath(edit.getTarget()).orElseThrow().stack();
+ stack.exit();
+ final EffectiveStatement<?, ?> parentStmt = stack.currentStatement();
+ verify(parentStmt instanceof SchemaNode, "Unexpected parent %s", parentStmt);
+ edit.setTargetSchemaNode(stack.toInference());
+ }
+
+ break;
+ case "value" :
+ // 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(value, in);
+ break;
+ default:
+ break;
+ }
+ }
+
+ in.endObject();
+
+ // read saved data to normalized node when target schema is already known
+ edit.setData(readEditData(new JsonReader(
+ new StringReader(value.toString())), edit.getTargetSchemaNode(), path));
+ }
+
+ /**
+ * Parse data defined in value node and saves it to buffer.
+ * @param value Buffer to read value node
+ * @param in JsonReader reader
+ * @throws IOException if operation fails
+ */
+ private void readValueNode(final @NonNull StringBuilder value, final @NonNull JsonReader in) throws IOException {
+ in.beginObject();
+ value.append('{');
+
+ value.append('"').append(in.nextName()).append("\":");
+
+ if (in.peek() == JsonToken.BEGIN_ARRAY) {
+ in.beginArray();
+ value.append('[');
+
+ while (in.hasNext()) {
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ } else {
+ readValueObject(value, in);
+ }
+ if (in.peek() != JsonToken.END_ARRAY) {
+ value.append(',');
+ }
+ }
+
+ in.endArray();
+ value.append(']');
+ } else {
+ readValueObject(value, in);
+ }
+
+ in.endObject();
+ value.append('}');
+ }
+
+ /**
+ * Parse one value object of data and saves it to buffer.
+ * @param value Buffer to read value object
+ * @param in JsonReader reader
+ * @throws IOException if operation fails
+ */
+ private void readValueObject(final @NonNull StringBuilder value, final @NonNull JsonReader in) throws IOException {
+ // read simple leaf value
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ return;
+ }
+
+ in.beginObject();
+ value.append('{');
+
+ while (in.hasNext()) {
+ value.append('"').append(in.nextName()).append("\":");
+
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ } else {
+ if (in.peek() == JsonToken.BEGIN_ARRAY) {
+ in.beginArray();
+ value.append('[');
+
+ while (in.hasNext()) {
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ } else {
+ readValueObject(value, in);
+ }
+ if (in.peek() != JsonToken.END_ARRAY) {
+ value.append(',');
+ }
+ }
+
+ in.endArray();
+ value.append(']');
+ } else {
+ readValueObject(value, in);
+ }
+ }
+
+ if (in.peek() != JsonToken.END_OBJECT) {
+ value.append(',');
+ }
+ }
+
+ in.endObject();
+ value.append('}');
+ }
+
+ /**
+ * Read patch edit data defined in value node to NormalizedNode.
+ * @param in reader JsonReader reader
+ * @return NormalizedNode representing data
+ */
+ private static NormalizedNode readEditData(final @NonNull JsonReader in,
+ final @NonNull Inference targetSchemaNode, final @NonNull InstanceIdentifierContext path) {
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+ final EffectiveModelContext context = path.getSchemaContext();
+ JsonParserStream.create(writer, JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context),
+ 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(final @NonNull PatchEdit edit) {
+ if (edit.getOperation() != null && edit.getTargetSchemaNode() != null
+ && checkDataPresence(edit.getOperation(), edit.getData() != null)) {
+ if (edit.getOperation().isWithValue()) {
+ // 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());
+ }
+
+ return new PatchEntity(edit.getId(), edit.getOperation(), edit.getTarget());
+ }
+
+ 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(final @NonNull 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 Inference targetSchemaNode;
+ private NormalizedNode data;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public PatchEditOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(final PatchEditOperation operation) {
+ this.operation = operation;
+ }
+
+ public YangInstanceIdentifier getTarget() {
+ return target;
+ }
+
+ public void setTarget(final YangInstanceIdentifier target) {
+ this.target = target;
+ }
+
+ public Inference getTargetSchemaNode() {
+ return targetSchemaNode;
+ }
+
+ public void setTargetSchemaNode(final Inference targetSchemaNode) {
+ this.targetSchemaNode = targetSchemaNode;
+ }
+
+ public NormalizedNode getData() {
+ return data;
+ }
+
+ public void setData(final NormalizedNode data) {
+ this.data = data;
+ }
+
+ public void clear() {
+ id = null;
+ operation = null;
+ target = null;
+ targetSchemaNode = null;
+ data = null;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java
new file mode 100644
index 0000000..d52de7d
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableMap;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+@Deprecated(forRemoval = true, since = "2.0.6")
+// Non-final for mocking
+public class NormalizedNodeContext {
+ private final InstanceIdentifierContext context;
+ private final ImmutableMap<String, Object> headers;
+ private final WriterParameters writerParameters;
+ private final NormalizedNode data;
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data, final WriterParameters writerParameters,
+ final ImmutableMap<String, Object> headers) {
+ this.context = context;
+ this.data = data;
+ this.writerParameters = writerParameters;
+ this.headers = requireNonNull(headers);
+ }
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data, final WriterParameters writerParameters) {
+ this(context, data, writerParameters, ImmutableMap.of());
+ }
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data) {
+ this(context, data, WriterParameters.EMPTY, ImmutableMap.of());
+ }
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data, final ImmutableMap<String, Object> headers) {
+ this(context, data, WriterParameters.EMPTY, headers);
+ }
+
+ public InstanceIdentifierContext getInstanceIdentifierContext() {
+ return context;
+ }
+
+ public NormalizedNode getData() {
+ return data;
+ }
+
+ public WriterParameters getWriterParameters() {
+ return writerParameters;
+ }
+
+ /**
+ * Return headers of {@code NormalizedNodeContext}.
+ *
+ * @return map of headers
+ */
+ public ImmutableMap<String, Object> getNewHeaders() {
+ return headers;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java
new file mode 100644
index 0000000..f754df8
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java
@@ -0,0 +1,200 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+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.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map.Entry;
+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.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.util.NetconfUtil;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
+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.JSONCodecFactorySupplier;
+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.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.xml.sax.SAXException;
+
+/**
+ * Normalized node writer for JSON.
+ *
+ * @deprecated This class will be replaced by NormalizedNodeJsonBodyWriter from restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Produces({
+ Draft02.MediaTypes.API + RestconfService.JSON,
+ Draft02.MediaTypes.DATA + RestconfService.JSON,
+ Draft02.MediaTypes.OPERATION + RestconfService.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 {
+ if (httpHeaders != null) {
+ for (final Entry<String, Object> entry : context.getNewHeaders().entrySet()) {
+ httpHeaders.add(entry.getKey(), entry.getValue());
+ }
+ }
+ NormalizedNode data = context.getData();
+ if (data == null) {
+ return;
+ }
+
+ final InstanceIdentifierContext identifierCtx = context.getInstanceIdentifierContext();
+
+ try (JsonWriter jsonWriter = createJsonWriter(entityStream, context.getWriterParameters().isPrettyPrint())) {
+ jsonWriter.beginObject();
+ writeNormalizedNode(jsonWriter, identifierCtx, data, context.getWriterParameters().getDepth());
+ jsonWriter.endObject();
+ jsonWriter.flush();
+ }
+ }
+
+ private static void writeNormalizedNode(final JsonWriter jsonWriter, final InstanceIdentifierContext context,
+ // Note: mutable argument
+ NormalizedNode data, final @Nullable Integer depth) throws IOException {
+
+ final var stack = context.inference().toSchemaInferenceStack();
+ final RestconfNormalizedNodeWriter nnWriter;
+ if (stack.isEmpty()) {
+ /*
+ * Creates writer without initialNs and we write children of root data container
+ * which is not visible in restconf
+ */
+ nnWriter = createNormalizedNodeWriter(context, context.inference(), jsonWriter, depth);
+ if (data instanceof ContainerNode) {
+ writeChildren(nnWriter,(ContainerNode) data);
+ } else if (data instanceof DOMSourceAnyxmlNode) {
+ try {
+ writeChildren(nnWriter,
+ (ContainerNode) NetconfUtil.transformDOMSourceToNormalizedNode(
+ context.getSchemaContext(), ((DOMSourceAnyxmlNode)data).body()).getResult());
+ } catch (XMLStreamException | URISyntaxException | SAXException e) {
+ throw new IOException("Cannot write anyxml.", e);
+ }
+ }
+ } else if (context.getSchemaNode() instanceof RpcDefinition) {
+ /*
+ * RpcDefinition is not supported as initial codec in JSONStreamWriter,
+ * so we need to emit initial output declaratation..
+ */
+ final var rpc = (RpcDefinition) context.getSchemaNode();
+ final var tmp = SchemaInferenceStack.of(context.getSchemaContext());
+ tmp.enterSchemaTree(rpc.getQName());
+ tmp.enterSchemaTree(rpc.getOutput().getQName());
+
+ nnWriter = createNormalizedNodeWriter(context, tmp.toInference(), jsonWriter, depth);
+ jsonWriter.name("output");
+ jsonWriter.beginObject();
+ writeChildren(nnWriter, (ContainerNode) data);
+ jsonWriter.endObject();
+ } else {
+ stack.exit();
+
+ if (data instanceof MapEntryNode) {
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .withChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, depth);
+ nnWriter.write(data);
+ }
+ nnWriter.flush();
+ }
+
+ private static void writeChildren(final RestconfNormalizedNodeWriter nnWriter, final ContainerNode data)
+ throws IOException {
+ for (final DataContainerChild child : data.body()) {
+ nnWriter.write(child);
+ }
+ }
+
+ private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(
+ final InstanceIdentifierContext context, final Inference inference, final JsonWriter jsonWriter,
+ final @Nullable Integer depth) {
+
+ final SchemaNode schema = context.getSchemaNode();
+ final JSONCodecFactory codecs = getCodecFactory(context);
+
+ final XMLNamespace initialNs;
+ if (schema instanceof DataSchemaNode && !((DataSchemaNode)schema).isAugmenting()
+ && !(schema instanceof SchemaContext) || schema instanceof RpcDefinition) {
+ initialNs = schema.getQName().getNamespace();
+ } else {
+ initialNs = null;
+ }
+ final NormalizedNodeStreamWriter streamWriter =
+ JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, inference, initialNs, jsonWriter);
+ if (depth != null) {
+ return DepthAwareNormalizedNodeWriter.forStreamWriter(streamWriter, depth);
+ }
+
+ return RestconfDelegatingNormalizedNodeWriter.forStreamWriter(streamWriter);
+ }
+
+ 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 JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context.getSchemaContext());
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java
new file mode 100644
index 0000000..e0ba9d9
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java
@@ -0,0 +1,181 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map.Entry;
+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.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.util.NetconfUtil;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
+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.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.xml.sax.SAXException;
+
+/**
+ * Normalized node writer for XML.
+ *
+ * @deprecated This class will be replaced by NormalizedNodeXmlBodyWriter from restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Produces({
+ Draft02.MediaTypes.API + RestconfService.XML,
+ Draft02.MediaTypes.DATA + RestconfService.XML,
+ Draft02.MediaTypes.OPERATION + RestconfService.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 {
+ for (final Entry<String, Object> entry : context.getNewHeaders().entrySet()) {
+ httpHeaders.add(entry.getKey(), entry.getValue());
+ }
+ 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();
+
+ writeNormalizedNode(xmlWriter, pathContext.inference().toSchemaInferenceStack(), pathContext, data,
+ context.getWriterParameters().getDepth());
+ }
+
+ private static void writeNormalizedNode(final XMLStreamWriter xmlWriter, final SchemaInferenceStack stack,
+ final InstanceIdentifierContext pathContext, NormalizedNode data, final @Nullable Integer depth)
+ throws IOException {
+ final RestconfNormalizedNodeWriter nnWriter;
+ final EffectiveModelContext schemaCtx = pathContext.getSchemaContext();
+ if (stack.isEmpty()) {
+ nnWriter = createNormalizedNodeWriter(xmlWriter, pathContext.inference(), depth);
+ if (data instanceof DOMSourceAnyxmlNode) {
+ try {
+ writeElements(xmlWriter, nnWriter,
+ (ContainerNode) NetconfUtil.transformDOMSourceToNormalizedNode(schemaCtx,
+ ((DOMSourceAnyxmlNode)data).body()).getResult());
+ } catch (XMLStreamException | URISyntaxException | SAXException e) {
+ throw new IOException("Cannot write anyxml", e);
+ }
+ } else {
+ writeElements(xmlWriter, nnWriter, (ContainerNode) data);
+ }
+ } else if (pathContext.getSchemaNode() instanceof RpcDefinition) {
+ final var rpc = (RpcDefinition) pathContext.getSchemaNode();
+ final var tmp = SchemaInferenceStack.of(pathContext.getSchemaContext());
+ tmp.enterSchemaTree(rpc.getQName());
+ tmp.enterSchemaTree(rpc.getOutput().getQName());
+
+ nnWriter = createNormalizedNodeWriter(xmlWriter, tmp.toInference(), depth);
+ writeElements(xmlWriter, nnWriter, (ContainerNode) data);
+ } else {
+ stack.exit();
+ nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth);
+ if (data instanceof MapEntryNode) {
+ // Restconf allows returning one list item. We need to wrap it
+ // in map node in order to serialize it properly
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .addChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter.write(data);
+ }
+ nnWriter.flush();
+ }
+
+ private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
+ final Inference inference, final @Nullable Integer depth) {
+ final NormalizedNodeStreamWriter xmlStreamWriter =
+ XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, inference);
+ if (depth != null) {
+ return DepthAwareNormalizedNodeWriter.forStreamWriter(xmlStreamWriter, depth);
+ }
+
+ return RestconfDelegatingNormalizedNodeWriter.forStreamWriter(xmlStreamWriter);
+ }
+
+ private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ final QName name = data.getIdentifier().getNodeType();
+ try {
+ xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, name.getLocalName(),
+ name.getNamespace().toString());
+ xmlWriter.writeDefaultNamespace(name.getNamespace().toString());
+ for (final NormalizedNode child : data.body()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+ xmlWriter.writeEndElement();
+ xmlWriter.flush();
+ } catch (final XMLStreamException e) {
+ throw new IOException("Failed to write elements", e);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java
new file mode 100644
index 0000000..63586af
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java
@@ -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.netconf.sal.rest.impl;
+
+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.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+
+
+@Provider
+@Produces({ Draft02.MediaTypes.PATCH_STATUS + RestconfService.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().elementBody());
+ jsonWriter.name("error-tag").value(restconfError.getErrorTag().elementBody());
+
+ // 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/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java
new file mode 100644
index 0000000..d941d6c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import 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.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+
+@Provider
+@Produces({ Draft02.MediaTypes.PATCH_STATUS + RestconfService.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 WebApplicationException {
+
+ try {
+ final XMLStreamWriter xmlWriter =
+ XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
+ writeDocument(xmlWriter, patchStatusContext);
+ } catch (final XMLStreamException | FactoryConfigurationError e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void writeDocument(final XMLStreamWriter writer, final PatchStatusContext context)
+ throws XMLStreamException {
+ 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 XMLStreamException {
+ writer.writeStartElement("errors");
+
+ for (final RestconfError restconfError : errors) {
+ writer.writeStartElement("error-type");
+ writer.writeCharacters(restconfError.getErrorType().elementBody());
+ writer.writeEndElement();
+
+ writer.writeStartElement("error-tag");
+ writer.writeCharacters(restconfError.getErrorTag().elementBody());
+ 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/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java
new file mode 100644
index 0000000..d8918df
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java
@@ -0,0 +1,72 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Application;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContentYangBodyWriter;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContentYinBodyWriter;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaRetrievalServiceImpl;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.StatisticsRestconfServiceWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+@Deprecated(since = "2.0.12")
+public class RestconfApplication extends Application {
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfApplication.class);
+
+ private final ControllerContext controllerContext;
+ private final StatisticsRestconfServiceWrapper statsServiceWrapper;
+
+ @Inject
+ public RestconfApplication(final ControllerContext controllerContext,
+ final StatisticsRestconfServiceWrapper statsServiceWrapper) {
+ this.controllerContext = controllerContext;
+ this.statsServiceWrapper = statsServiceWrapper;
+ LOG.warn("Pre-standard version of RESTCONF activated. Please note that this implementation is considered "
+ + "obsoleve and WILL BE REMOVED IN THE NEXT MAJOR RELEASE. Please use the RFC8040-compliant "
+ + "implementation instead.");
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return ImmutableSet.<Class<?>>builder()
+ .add(PatchJsonBodyWriter.class)
+ .add(PatchXmlBodyWriter.class)
+ .add(NormalizedNodeJsonBodyWriter.class)
+ .add(NormalizedNodeXmlBodyWriter.class)
+ .add(SchemaExportContentYinBodyWriter.class)
+ .add(SchemaExportContentYangBodyWriter.class)
+ .build();
+ }
+
+ @Override
+ public Set<Object> getSingletons() {
+ final Set<Object> singletons = new HashSet<>();
+ final SchemaRetrievalServiceImpl schemaRetrieval = new SchemaRetrievalServiceImpl(controllerContext);
+ singletons.add(schemaRetrieval);
+ singletons.add(new RestconfCompositeWrapper(statsServiceWrapper, schemaRetrieval));
+ singletons.add(new RestconfDocumentedExceptionMapper(controllerContext));
+ singletons.add(new XmlNormalizedNodeBodyReader(controllerContext));
+ singletons.add(new JsonNormalizedNodeBodyReader(controllerContext));
+ singletons.add(new XmlToPatchBodyReader(controllerContext));
+ singletons.add(new JsonToPatchBodyReader(controllerContext));
+// singletons.add(StructuredDataToXmlProvider.INSTANCE);
+// singletons.add(StructuredDataToJsonProvider.INSTANCE);
+// singletons.add(JsonToCompositeNodeProvider.INSTANCE);
+// singletons.add(XmlToCompositeNodeProvider.INSTANCE);
+ return singletons;
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java
new file mode 100644
index 0000000..90b83a0
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaRetrievalService;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+
+public class RestconfCompositeWrapper implements RestconfService, SchemaRetrievalService {
+
+ private final RestconfService restconf;
+ private final SchemaRetrievalService schema;
+
+ public RestconfCompositeWrapper(final RestconfService restconf, final SchemaRetrievalService schema) {
+ this.restconf = requireNonNull(restconf);
+ this.schema = requireNonNull(schema);
+ }
+
+ @Override
+ public Object getRoot() {
+ return this.restconf.getRoot();
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final UriInfo uriInfo) {
+ return this.restconf.getModules(uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.getModules(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.getModule(identifier, uriInfo);
+ }
+
+ @Override
+ public String getOperationsJSON() {
+ return this.restconf.getOperationsJSON();
+ }
+
+ @Override
+ public String getOperationsXML() {
+ return this.restconf.getOperationsXML();
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.getOperations(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.invokeRpc(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.readConfigurationData(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.readOperationalData(identifier, uriInfo);
+ }
+
+ @Override
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.updateConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.createConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return this.restconf.createConfigurationData(payload, uriInfo);
+ }
+
+ @Override
+ public Response deleteConfigurationData(final String identifier) {
+ return this.restconf.deleteConfigurationData(identifier);
+ }
+
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.subscribeToStream(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
+ return this.restconf.getAvailableStreams(uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.patchConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final PatchContext context, final UriInfo uriInfo) {
+ return this.restconf.patchConfigurationData(context, uriInfo);
+ }
+
+ @Override
+ public SchemaExportContext getSchema(final String mountId) {
+ return this.schema.getSchema(mountId);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java
new file mode 100644
index 0000000..aaa1ccc
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import java.io.IOException;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+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.api.schema.stream.NormalizedNodeWriter;
+
+/**
+ * This class just delegates all of the functionality to Yangtools normalized node writer.
+ */
+public final class RestconfDelegatingNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
+ private final NormalizedNodeWriter delegNNWriter;
+
+ private RestconfDelegatingNormalizedNodeWriter(final NormalizedNodeStreamWriter streamWriter, final boolean
+ orderKeyLeaves) {
+ this.delegNNWriter = NormalizedNodeWriter.forStreamWriter(streamWriter, orderKeyLeaves);
+ }
+
+ public static RestconfDelegatingNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer) {
+ return forStreamWriter(writer, true);
+ }
+
+ public static RestconfDelegatingNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final boolean orderKeyLeaves) {
+ return new RestconfDelegatingNormalizedNodeWriter(writer, orderKeyLeaves);
+ }
+
+ @Override
+ public RestconfDelegatingNormalizedNodeWriter write(final NormalizedNode node) throws IOException {
+ delegNNWriter.write(node);
+ return this;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegNNWriter.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegNNWriter.close();
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java
new file mode 100644
index 0000000..ff0be39
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+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.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.ErrorTags;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+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.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.ForwardingNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
+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.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class defines an ExceptionMapper that handles RestconfDocumentedExceptions thrown by resource implementations
+ * and translates appropriately to restconf error response as defined in the RESTCONF RFC draft.
+ *
+ * @author Thomas Pantelis
+ */
+@Provider
+public class RestconfDocumentedExceptionMapper implements ExceptionMapper<RestconfDocumentedException> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapper.class);
+
+ private static final XMLOutputFactory XML_FACTORY;
+
+ static {
+ XML_FACTORY = XMLOutputFactory.newFactory();
+ XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+ }
+
+ @Context
+ private HttpHeaders headers;
+
+ private final ControllerContext controllerContext;
+
+ public RestconfDocumentedExceptionMapper(final ControllerContext controllerContext) {
+ this.controllerContext = requireNonNull(controllerContext);
+ }
+
+ @Override
+ public Response toResponse(final RestconfDocumentedException exception) {
+
+ LOG.debug("In toResponse: {}", exception.getMessage());
+
+ final List<MediaType> mediaTypeList = new ArrayList<>();
+ if (headers.getMediaType() != null) {
+ mediaTypeList.add(headers.getMediaType());
+ }
+
+ mediaTypeList.addAll(headers.getAcceptableMediaTypes());
+ final MediaType mediaType = mediaTypeList.stream().filter(type -> !type.equals(MediaType.WILDCARD_TYPE))
+ .findFirst().orElse(MediaType.APPLICATION_JSON_TYPE);
+
+ LOG.debug("Using MediaType: {}", mediaType);
+
+ final List<RestconfError> errors = exception.getErrors();
+ if (errors.isEmpty()) {
+ // We don't actually want to send any content but, if we don't set any content here,
+ // the tomcat front-end will send back an html error report. To prevent that, set a
+ // single space char in the entity.
+
+ return Response.status(exception.getStatus()).type(MediaType.TEXT_PLAIN_TYPE).entity(" ").build();
+ }
+
+ final Status status = ErrorTags.statusOf(errors.iterator().next().getErrorTag());
+ final var errorsEntry = controllerContext.getRestconfModuleErrorsSchemaNode();
+ if (errorsEntry == null) {
+ return Response.status(status).type(MediaType.TEXT_PLAIN_TYPE).entity(exception.getMessage()).build();
+ }
+
+ final var errorsSchemaNode = errorsEntry.getValue();
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> errContBuild =
+ SchemaAwareBuilders.containerBuilder(errorsSchemaNode);
+
+ final var schemaList = ControllerContext.findInstanceDataChildrenByName(errorsSchemaNode,
+ Draft02.RestConfModule.ERROR_LIST_SCHEMA_NODE);
+ final DataSchemaNode errListSchemaNode = ControllerContext.getFirst(schemaList);
+ checkState(errListSchemaNode instanceof ListSchemaNode, "Found Error SchemaNode isn't ListSchemaNode");
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listErorsBuilder = SchemaAwareBuilders
+ .mapBuilder((ListSchemaNode) errListSchemaNode);
+
+
+ for (final RestconfError error : errors) {
+ listErorsBuilder.withChild(toErrorEntryNode(error, errListSchemaNode));
+ }
+ errContBuild.withChild(listErorsBuilder.build());
+
+ final NormalizedNodeContext errContext = new NormalizedNodeContext(
+ InstanceIdentifierContext.ofStack(errorsEntry.getKey(), null), errContBuild.build());
+
+ final String responseBody;
+ if (mediaType.getSubtype().endsWith("json")) {
+ responseBody = toJsonResponseBody(errContext);
+ } else {
+ responseBody = toXMLResponseBody(errContext);
+ }
+
+ return Response.status(status).type(mediaType).entity(responseBody).build();
+ }
+
+ private static MapEntryNode toErrorEntryNode(final RestconfError error, final DataSchemaNode errListSchemaNode) {
+ checkArgument(errListSchemaNode instanceof ListSchemaNode,
+ "errListSchemaNode has to be of type ListSchemaNode");
+ final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) errListSchemaNode;
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> errNodeValues = SchemaAwareBuilders
+ .mapEntryBuilder(listStreamSchemaNode);
+
+ var lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-type");
+ final DataSchemaNode errTypSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errTypSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errTypSchemaNode)
+ .withValue(error.getErrorType().elementBody()).build());
+
+ lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-tag");
+ final DataSchemaNode errTagSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errTagSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errTagSchemaNode)
+ .withValue(error.getErrorTag().elementBody()).build());
+
+ if (error.getErrorAppTag() != null) {
+ lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-app-tag");
+ final DataSchemaNode errAppTagSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errAppTagSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errAppTagSchemaNode)
+ .withValue(error.getErrorAppTag()).build());
+ }
+
+ lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-message");
+ final DataSchemaNode errMsgSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errMsgSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errMsgSchemaNode)
+ .withValue(error.getErrorMessage()).build());
+
+ if (error.getErrorInfo() != null) {
+ // Oddly, error-info is defined as an empty container in the restconf yang. Apparently the
+ // intention is for implementors to define their own data content so we'll just treat it as a leaf
+ // with string data.
+ errNodeValues.withChild(ImmutableNodes.leafNode(Draft02.RestConfModule.ERROR_INFO_QNAME,
+ error.getErrorInfo()));
+ }
+
+ // TODO : find how could we add possible "error-path"
+
+ return errNodeValues.build();
+ }
+
+ private static String toJsonResponseBody(final NormalizedNodeContext errorsNode) {
+ final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ NormalizedNode data = errorsNode.getData();
+ final InstanceIdentifierContext context = errorsNode.getInstanceIdentifierContext();
+ final DataSchemaNode schema = (DataSchemaNode) context.getSchemaNode();
+
+ final OutputStreamWriter outputWriter = new OutputStreamWriter(outStream, StandardCharsets.UTF_8);
+ if (data == null) {
+ throw new RestconfDocumentedException(Response.Status.NOT_FOUND);
+ }
+
+ final boolean isDataRoot;
+ final var stack = context.inference().toSchemaInferenceStack();
+ if (stack.isEmpty()) {
+ isDataRoot = true;
+ } else {
+ isDataRoot = false;
+ stack.exit();
+ // FIXME: Add proper handling of reading root.
+ }
+
+ XMLNamespace initialNs = null;
+ if (!schema.isAugmenting() && !(schema instanceof SchemaContext)) {
+ initialNs = schema.getQName().getNamespace();
+ }
+
+ final JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(outputWriter);
+ final NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+ JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context.getSchemaContext()),
+ stack.toInference(), initialNs, jsonWriter);
+
+ // We create a delegating writer to special-case error-info as error-info is defined as an empty
+ // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+ // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+ // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+ // for error-info.
+ final NormalizedNodeStreamWriter streamWriter = new ForwardingNormalizedNodeStreamWriter() {
+ private boolean inOurLeaf;
+
+ @Override
+ protected NormalizedNodeStreamWriter delegate() {
+ return jsonStreamWriter;
+ }
+
+ @Override
+ public void startLeafNode(final NodeIdentifier name) throws IOException {
+ if (name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+ inOurLeaf = true;
+ jsonWriter.name(Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName());
+ } else {
+ super.startLeafNode(name);
+ }
+ }
+
+ @Override
+ public void scalarValue(final Object value) throws IOException {
+ if (inOurLeaf) {
+ jsonWriter.value(value.toString());
+ } else {
+ super.scalarValue(value);
+ }
+ }
+
+ @Override
+ public void endNode() throws IOException {
+ if (inOurLeaf) {
+ inOurLeaf = false;
+ } else {
+ super.endNode();
+ }
+ }
+ };
+
+ final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
+ try {
+ if (isDataRoot) {
+ writeDataRoot(outputWriter,nnWriter,(ContainerNode) data);
+ } else {
+ if (data instanceof MapEntryNode) {
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .withChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter.write(data);
+ }
+ nnWriter.flush();
+ outputWriter.flush();
+ } catch (final IOException e) {
+ LOG.warn("Error writing error response body", e);
+ }
+
+ try {
+ streamWriter.close();
+ } catch (IOException e) {
+ LOG.warn("Failed to close stream writer", e);
+ }
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static String toXMLResponseBody(final NormalizedNodeContext errorsNode) {
+ final InstanceIdentifierContext pathContext = errorsNode.getInstanceIdentifierContext();
+ final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+
+ final XMLStreamWriter xmlWriter;
+ try {
+ xmlWriter = XML_FACTORY.createXMLStreamWriter(outStream, StandardCharsets.UTF_8.name());
+ } catch (final XMLStreamException | FactoryConfigurationError e) {
+ throw new IllegalStateException(e);
+ }
+ NormalizedNode data = errorsNode.getData();
+
+ final boolean isDataRoot;
+ final var stack = pathContext.inference().toSchemaInferenceStack();
+ if (stack.isEmpty()) {
+ isDataRoot = true;
+ } else {
+ isDataRoot = false;
+ stack.exit();
+ }
+
+ final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
+ stack.toInference());
+
+ // We create a delegating writer to special-case error-info as error-info is defined as an empty
+ // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+ // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+ // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+ // for error-info.
+ final NormalizedNodeStreamWriter streamWriter = new ForwardingNormalizedNodeStreamWriter() {
+ private boolean inOurLeaf;
+
+ @Override
+ protected NormalizedNodeStreamWriter delegate() {
+ return xmlStreamWriter;
+ }
+
+ @Override
+ public void startLeafNode(final NodeIdentifier name) throws IOException {
+ if (name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+ String ns = Draft02.RestConfModule.ERROR_INFO_QNAME.getNamespace().toString();
+ try {
+ xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
+ Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName(), ns);
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ inOurLeaf = true;
+ } else {
+ super.startLeafNode(name);
+ }
+ }
+
+ @Override
+ public void scalarValue(final Object value) throws IOException {
+ if (inOurLeaf) {
+ try {
+ xmlWriter.writeCharacters(value.toString());
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ } else {
+ super.scalarValue(value);
+ }
+ }
+
+ @Override
+ public void endNode() throws IOException {
+ if (inOurLeaf) {
+ try {
+ xmlWriter.writeEndElement();
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ inOurLeaf = false;
+ } else {
+ super.endNode();
+ }
+ }
+ };
+
+ final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
+ try {
+ if (isDataRoot) {
+ writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
+ } else {
+ if (data instanceof MapEntryNode) {
+ // Restconf allows returning one list item. We need to wrap it
+ // in map node in order to serialize it properly
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .addChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter.write(data);
+ nnWriter.flush();
+ }
+ } catch (final IOException e) {
+ LOG.warn("Error writing error response body.", e);
+ }
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ final QName name = SchemaContext.NAME;
+ try {
+ xmlWriter.writeStartElement(name.getNamespace().toString(), name.getLocalName());
+ for (final DataContainerChild child : data.body()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+ xmlWriter.writeEndElement();
+ xmlWriter.flush();
+ } catch (final XMLStreamException e) {
+ throw new IOException("Failed to write elements", e);
+ }
+ }
+
+ private static void writeDataRoot(final OutputStreamWriter outputWriter, final NormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ for (final DataContainerChild child : data.body()) {
+ nnWriter.write(child);
+ nnWriter.flush();
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java
new file mode 100644
index 0000000..b61ed58
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java
@@ -0,0 +1,62 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Codec for module instance identifiers.
+ *
+ * @deprecated This class will be replaced by StringModuleInstanceIdentifierCodec from restconf-nb-rfc8040
+ */
+@Deprecated
+public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+
+ private final DataSchemaContextTree dataContextTree;
+ private final SchemaContext context;
+ private final String defaultPrefix;
+
+ public StringModuleInstanceIdentifierCodec(final EffectiveModelContext context) {
+ this.context = requireNonNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ this.defaultPrefix = "";
+ }
+
+ StringModuleInstanceIdentifierCodec(final EffectiveModelContext context, final @NonNull String defaultPrefix) {
+ this.context = requireNonNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ this.defaultPrefix = defaultPrefix;
+ }
+
+ @Override
+ protected Module moduleForPrefix(final String prefix) {
+ if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
+ return this.context.findModules(this.defaultPrefix).stream().findFirst().orElse(null);
+ } else {
+ return this.context.findModules(prefix).stream().findFirst().orElse(null);
+ }
+ }
+
+ @Override
+ protected DataSchemaContextTree getDataContextTree() {
+ return this.dataContextTree;
+ }
+
+ @Override
+ protected String prefixForNamespace(final XMLNamespace namespace) {
+ return this.context.findModules(namespace).stream().findFirst().map(Module::getName).orElse(null);
+ }
+} \ No newline at end of file
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java
new file mode 100644
index 0000000..9e21659
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+public class UnsupportedFormatException extends Exception {
+
+ private static final long serialVersionUID = -1741388894406313402L;
+
+ public UnsupportedFormatException() {
+ }
+
+ public UnsupportedFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnsupportedFormatException(String message) {
+ super(message);
+ }
+
+ public UnsupportedFormatException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java
new file mode 100644
index 0000000..1ad6985
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+@Deprecated(forRemoval = true, since = "2.0.6")
+public final class WriterParameters {
+ static final WriterParameters EMPTY = new WriterParametersBuilder().build();
+
+ private final Integer depth;
+ private final boolean prettyPrint;
+
+ private WriterParameters(final WriterParametersBuilder builder) {
+ depth = builder.depth;
+ prettyPrint = builder.prettyPrint;
+ }
+
+ public Integer getDepth() {
+ return depth;
+ }
+
+ public boolean isPrettyPrint() {
+ return prettyPrint;
+ }
+
+ @Deprecated(forRemoval = true, since = "2.0.6")
+ public static final class WriterParametersBuilder {
+ private Integer depth;
+ private boolean prettyPrint;
+
+ public WriterParametersBuilder setDepth(final int depth) {
+ this.depth = depth;
+ return this;
+ }
+
+ public WriterParametersBuilder setPrettyPrint(final boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ return this;
+ }
+
+ public WriterParameters build() {
+ return new WriterParameters(this);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
new file mode 100644
index 0000000..84a7978
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
@@ -0,0 +1,200 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+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.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.ContainerLike;
+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.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+@Provider
+@Consumes({
+ Draft02.MediaTypes.DATA + RestconfService.XML,
+ Draft02.MediaTypes.OPERATION + RestconfService.XML,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+})
+public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsProvider
+ implements MessageBodyReader<NormalizedNodeContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlNormalizedNodeBodyReader.class);
+
+ public XmlNormalizedNodeBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readFrom(final Class<NormalizedNodeContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws
+ WebApplicationException {
+ try {
+ return readFrom(entityStream);
+ } catch (final RestconfDocumentedException e) {
+ throw e;
+ } catch (final Exception e) {
+ LOG.debug("Error parsing xml input", e);
+ RestconfDocumentedException.throwIfYangError(e);
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+ }
+
+ private NormalizedNodeContext readFrom(final InputStream entityStream) throws IOException, SAXException,
+ XMLStreamException, ParserConfigurationException, URISyntaxException {
+ final InstanceIdentifierContext path = getInstanceIdentifierContext();
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ // represent empty nopayload input
+ return new NormalizedNodeContext(path, null);
+ }
+
+ final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
+ return parse(path, doc);
+ }
+
+ private NormalizedNodeContext parse(final InstanceIdentifierContext pathContext,final Document doc)
+ throws XMLStreamException, IOException, SAXException, URISyntaxException {
+ final SchemaNode schemaNodeContext = pathContext.getSchemaNode();
+ DataSchemaNode schemaNode;
+ final List<PathArgument> iiToDataList = new ArrayList<>();
+ Inference inference;
+ if (schemaNodeContext instanceof RpcDefinition) {
+ schemaNode = ((RpcDefinition) schemaNodeContext).getInput();
+ inference = pathContext.inference();
+ } else if (schemaNodeContext instanceof DataSchemaNode) {
+ schemaNode = (DataSchemaNode) schemaNodeContext;
+
+ final String docRootElm = doc.getDocumentElement().getLocalName();
+ final XMLNamespace docRootNamespace = XMLNamespace.of(doc.getDocumentElement().getNamespaceURI());
+
+ if (isPost()) {
+ final var context = pathContext.getSchemaContext();
+ final var it = context.findModuleStatements(docRootNamespace).iterator();
+ checkState(it.hasNext(), "Failed to find module for %s", docRootNamespace);
+ final var qname = QName.create(it.next().localQNameModule(), docRootElm);
+
+ final var nodeAndStack = DataSchemaContextTree.from(context)
+ .enterPath(pathContext.getInstanceIdentifier()).orElseThrow();
+
+ final var stack = nodeAndStack.stack();
+ var current = nodeAndStack.node();
+ do {
+ final var next = current.enterChild(stack, qname);
+ checkState(next != null, "Child \"%s\" was not found in parent schema node \"%s\"", qname,
+ schemaNode);
+ iiToDataList.add(next.getIdentifier());
+ schemaNode = next.getDataSchemaNode();
+ current = next;
+ } while (current.isMixin());
+
+ // We need to unwind the last identifier if it a NodeIdentifierWithPredicates, as it does not have
+ // any predicates at all. The real identifier is then added below
+ if (stack.currentStatement() instanceof ListEffectiveStatement) {
+ iiToDataList.remove(iiToDataList.size() - 1);
+ }
+
+ inference = stack.toInference();
+
+ } else {
+ // PUT
+ final QName scQName = schemaNode.getQName();
+ checkState(docRootElm.equals(scQName.getLocalName()) && docRootNamespace.equals(scQName.getNamespace()),
+ "Not correct message root element \"%s\", should be \"%s\"", docRootElm, scQName);
+ inference = pathContext.inference();
+ }
+ } else {
+ throw new IllegalStateException("Unknown SchemaNode");
+ }
+
+
+ NormalizedNode parsed;
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ if (schemaNode instanceof ContainerLike || schemaNode instanceof ListSchemaNode
+ || schemaNode instanceof LeafSchemaNode) {
+ final XmlParserStream xmlParser = XmlParserStream.create(writer, inference);
+ 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.body().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;
+ }
+
+ return new NormalizedNodeContext(pathContext.withConcatenatedArgs(iiToDataList), parsed);
+ }
+}
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java
new file mode 100644
index 0000000..9cc4766
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+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.common.util.RestUtil;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+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.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+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;
+
+/**
+ * Yang PATCH Reader for XML.
+ *
+ * @deprecated This class will be replaced by XmlToPatchBodyReader from restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.XML})
+public class XmlToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider implements
+ MessageBodyReader<PatchContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlToPatchBodyReader.class);
+
+ public XmlToPatchBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchContext readFrom(final Class<PatchContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+ throws WebApplicationException {
+
+ try {
+ final InstanceIdentifierContext path = getInstanceIdentifierContext();
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ // represent empty nopayload input
+ return new PatchContext(path, null, null);
+ }
+
+ final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
+ return parse(path, doc);
+ } catch (final RestconfDocumentedException e) {
+ throw e;
+ } catch (final Exception e) {
+ LOG.debug("Error parsing xml input", e);
+ RestconfDocumentedException.throwIfYangError(e);
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+ }
+
+ 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(Locale.ROOT));
+
+ 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().findModules(XMLNamespace.of(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;
+ final Inference inference;
+ if (target.equals("/")) {
+ targetII = pathContext.getInstanceIdentifier();
+ targetNode = pathContext.getSchemaContext();
+ inference = pathContext.inference();
+ } else {
+ targetII = codec.deserialize(codec.serialize(pathContext.getInstanceIdentifier())
+ .concat(prepareNonCondXpath(schemaNode, target.replaceFirst("/", ""), firstValueElement,
+ namespace,
+ module.getQNameModule().getRevision().map(Revision::toString).orElse(null))));
+ // move schema node
+ final var result = codec.getDataContextTree().enterPath(targetII).orElseThrow();
+ schemaNode = result.node().getDataSchemaNode();
+
+ final var stack = result.stack();
+ inference = stack.toInference();
+
+ stack.exit();
+ final EffectiveStatement<?, ?> parentStmt = stack.currentStatement();
+ verify(parentStmt instanceof SchemaNode, "Unexpected parent %s", parentStmt);
+ targetNode = (SchemaNode) parentStmt;
+ }
+
+ 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, inference);
+ 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(final @NonNull Element element,
+ final @NonNull 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(final @NonNull DataSchemaNode schemaNode, final @NonNull String target,
+ final @NonNull Element value, final @NonNull String namespace, final @NonNull String revision) {
+ final Iterator<String> args = Splitter.on("/").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('/').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(final @NonNull Element value, final @NonNull 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(final @NonNull StringBuilder nonCondXpath, final @NonNull Iterator<QName> keyNames,
+ final @NonNull Iterator<String> keyValues) {
+ while (keyNames.hasNext()) {
+ nonCondXpath.append("[").append(keyNames.next().getLocalName()).append("='").append(keyValues.next())
+ .append("']");
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java
new file mode 100644
index 0000000..07eb8e2
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java
@@ -0,0 +1,8 @@
+/*
+ * 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.netconf.sal.rest.impl; \ No newline at end of file