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