From: Peter Suna Date: Mon, 18 Sep 2023 11:21:18 +0000 (+0200) Subject: JSON: Resolve 500 response from device exception X-Git-Tag: v7.0.0~152 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=cf16bd571f855d4458758f97ca390a2f146b377f;p=netconf.git JSON: Resolve 500 response from device exception Utilize a custom JsonWriter to prepare the ietf-restconf error response body. To emit the error-path value, use the JSONCodec from the device to generate the correct path format based on the device's model context. JIRA: NETCONF-1130 Change-Id: Id82849cabf3fd99b22b4d95eaf9ff5a3ce815b8b Signed-off-by: Peter Suna Signed-off-by: Yaroslav Lastivka Signed-off-by: Ivan Hrasko --- diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/JsonStreamWriterWithDisabledValidation.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/JsonStreamWriterWithDisabledValidation.java index 0c6a790af6..a2472f235f 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/JsonStreamWriterWithDisabledValidation.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/JsonStreamWriterWithDisabledValidation.java @@ -21,6 +21,7 @@ import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory; /** * JSON stream-writer with disabled leaf-type validation for specified QName. */ +// FIXME remove this class final class JsonStreamWriterWithDisabledValidation extends StreamWriterWithDisabledValidation { private static final int DEFAULT_INDENT_SPACES_NUM = 2; private static final XMLNamespace IETF_RESTCONF_URI = Errors.QNAME.getModule().getNamespace(); diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java index 13e27e5b74..a4ebf256e8 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapper.java @@ -33,6 +33,7 @@ import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.nb.jaxrs.JaxRsMediaTypes; import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags; +import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.restconf.server.spi.DatabindProvider; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.errors.Errors; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.errors.errors.Error; @@ -42,6 +43,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.slf4j.Logger; @@ -66,8 +68,10 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< private static final QName ERROR_TAG_QNAME = qnameOf("error-tag"); private static final QName ERROR_APP_TAG_QNAME = qnameOf("error-app-tag"); private static final QName ERROR_MESSAGE_QNAME = qnameOf("error-message"); - private static final QName ERROR_PATH_QNAME = qnameOf("error-path"); + // FIXME make this private static final QName ERROR_INFO_QNAME = qnameOf("error-info"); + private static final QName ERROR_PATH_QNAME = qnameOf("error-path"); + private static final int DEFAULT_INDENT_SPACES_NUM = 2; private final DatabindProvider databindProvider; @@ -101,7 +105,7 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< final String serializedResponseBody; final MediaType responseMediaType = transformToResponseMediaType(getSupportedMediaType()); if (JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON.equals(responseMediaType)) { - serializedResponseBody = serializeErrorsContainerToJson(errorsContainer); + serializedResponseBody = serializeExceptionToJson(exception, databindProvider); } else { serializedResponseBody = serializeErrorsContainerToXml(errorsContainer); } @@ -167,19 +171,55 @@ public final class RestconfDocumentedExceptionMapper implements ExceptionMapper< } /** - * Serialization of the errors container into JSON representation. + * Serialization exceptions into JSON representation. * - * @param errorsContainer To be serialized errors container. - * @return JSON representation of the errors container. + * @param exception To be serialized exception. + * @param databindProvider Holder of current {@code DatabindContext}. + * @return JSON representation of the exception. */ - private String serializeErrorsContainerToJson(final ContainerNode errorsContainer) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStreamWriter streamStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - ) { - return writeNormalizedNode(errorsContainer, outputStream, - new JsonStreamWriterWithDisabledValidation(databindProvider.currentDatabind(), streamStreamWriter)); + private static String serializeExceptionToJson(final RestconfDocumentedException exception, + final DatabindProvider databindProvider) { + try (var outputStream = new ByteArrayOutputStream(); + var streamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); + var jsonWriter = JsonWriterFactory.createJsonWriter(streamWriter, DEFAULT_INDENT_SPACES_NUM)) { + final var currentDatabindContext = exception.modelContext() != null + ? DatabindContext.ofModel(exception.modelContext()) : databindProvider.currentDatabind(); + jsonWriter.beginObject(); + final var errors = exception.getErrors(); + if (errors != null && !errors.isEmpty()) { + jsonWriter.name(Errors.QNAME.getLocalName()).beginObject(); + jsonWriter.name(Error.QNAME.getLocalName()).beginArray(); + for (final var error : errors) { + jsonWriter.beginObject() + .name(ERROR_TAG_QNAME.getLocalName()).value(error.getErrorTag().elementBody()); + final var errorAppTag = error.getErrorAppTag(); + if (errorAppTag != null) { + jsonWriter.name(ERROR_APP_TAG_QNAME.getLocalName()).value(errorAppTag); + } + final var errorInfo = error.getErrorInfo(); + if (errorInfo != null) { + jsonWriter.name(ERROR_INFO_QNAME.getLocalName()).value(errorInfo); + } + final var errorMessage = error.getErrorMessage(); + if (errorMessage != null) { + jsonWriter.name(ERROR_MESSAGE_QNAME.getLocalName()).value(errorMessage); + } + final var errorPath = error.getErrorPath(); + if (errorPath != null) { + jsonWriter.name(ERROR_PATH_QNAME.getLocalName()); + currentDatabindContext.jsonCodecs().instanceIdentifierCodec() + .writeValue(jsonWriter, errorPath); + } + jsonWriter.name(ERROR_TYPE_QNAME.getLocalName()).value(error.getErrorType().elementBody()); + jsonWriter.endObject(); + } + jsonWriter.endArray().endObject(); + } + jsonWriter.endObject(); + streamWriter.flush(); + return outputStream.toString(StandardCharsets.UTF_8); } catch (IOException e) { - throw new IllegalStateException("Cannot close some of the output JSON writers", e); + throw new IllegalStateException("Error while serializing restconf exception into JSON", e); } } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/StreamWriterWithDisabledValidation.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/StreamWriterWithDisabledValidation.java index 37c2523b40..e81b39517f 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/StreamWriterWithDisabledValidation.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/StreamWriterWithDisabledValidation.java @@ -21,6 +21,7 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference * the schema and thus will expect a LeafSchemaNode but the schema has a ContainerSchemaNode so, to avoid an error, * we override the leafNode behavior for error-info. */ +// FIXME remove this class abstract class StreamWriterWithDisabledValidation extends ForwardingNormalizedNodeStreamWriter { private boolean inOurLeaf; diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/XmlStreamWriterWithDisabledValidation.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/XmlStreamWriterWithDisabledValidation.java index b8303d1afd..18ace4e71a 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/XmlStreamWriterWithDisabledValidation.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/XmlStreamWriterWithDisabledValidation.java @@ -23,6 +23,7 @@ import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStr /** * XML stream-writer with disabled leaf-type validation for specified QName. */ +// FIXME remove this class final class XmlStreamWriterWithDisabledValidation extends StreamWriterWithDisabledValidation { private static final XMLOutputFactory XML_FACTORY; diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java index bda623865d..a93f116b5c 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/errors/RestconfDocumentedExceptionMapperTest.java @@ -216,7 +216,7 @@ public class RestconfDocumentedExceptionMapperTest { } @Test - public void testFormatingJson() throws JSONException { + public void testFormattingJson() throws JSONException { assumeTrue(expectedResponse.getMediaType().equals(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON)); exceptionMapper.setHttpHeaders(httpHeaders); @@ -246,4 +246,4 @@ public class RestconfDocumentedExceptionMapperTest { JSONAssert.assertEquals(expectedResponseInJson, actualResponseInJson, true); } } -} \ No newline at end of file +}