Introduce RpcMessage and RpcReplyMessage 73/107373/13
authormatus.matok <matus.matok@pantheon.tech>
Tue, 8 Aug 2023 12:09:00 +0000 (14:09 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 29 Aug 2023 11:02:44 +0000 (13:02 +0200)
Introduced RpcMessage and RpcReplyMessage both extending NetconfMessage, with
basic wrapping functionality. Added tests to verify correct wrapping.

JIRA: NETCONF-1014
Change-Id: I72d5d03dde6553201d2fca4db1faf5d0a844d872
Signed-off-by: matus.matok <matus.matok@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/DocumentedException.java
protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/messages/RpcMessage.java [new file with mode: 0644]
protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/messages/RpcReplyMessage.java [new file with mode: 0644]
protocol/netconf-api/src/test/java/org/opendaylight/netconf/api/messages/RpcMessageTest.java [new file with mode: 0644]
protocol/netconf-api/src/test/java/org/opendaylight/netconf/api/messages/RpcReplyMessageTest.java [new file with mode: 0644]
protocol/netconf-server/src/main/java/org/opendaylight/netconf/server/SendErrorExceptionUtil.java

index 48d8f76cbee73b365ac5033892ffb0588d3a630e..6161cc6da2da625921995a971b7b7d47345f189b 100644 (file)
@@ -11,7 +11,6 @@ import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
-import java.io.Serial;
 import java.util.HashMap;
 import java.util.Map;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -19,8 +18,6 @@ import javax.xml.parsers.ParserConfigurationException;
 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
@@ -47,9 +44,9 @@ public class DocumentedException extends Exception {
     public static final String ERROR_MESSAGE = "error-message";
     public static final String ERROR_INFO = "error-info";
 
-    @Serial
+    @java.io.Serial
     private static final long serialVersionUID = 1L;
-    private static final Logger LOG = LoggerFactory.getLogger(DocumentedException.class);
+
     private static final DocumentBuilderFactory BUILDER_FACTORY;
 
     static {
@@ -226,39 +223,38 @@ public class DocumentedException extends Exception {
 
     // FIXME: this really should be an spi/util method (or even netconf-util-w3c-dom?) as this certainly is not the
     //        primary interface we want to expose -- it is inherently mutable and its API is a pure nightmare.
-    public Document toXMLDocument() {
-        Document doc = null;
+    public final Document toXMLDocument() {
+        final Document doc;
         try {
             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
+        } catch (final ParserConfigurationException e) {
+            throw new IllegalStateException("Error outputting to XML document", e);
+        }
 
-            final var rpcReply = doc.createElementNS(NamespaceURN.BASE, RPC_REPLY_KEY);
-            doc.appendChild(rpcReply);
-
-            final var rpcError = doc.createElementNS(NamespaceURN.BASE, RPC_ERROR);
-            rpcReply.appendChild(rpcError);
-
-            rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().elementBody()));
-            rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().elementBody()));
-            rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
-            rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
-
-            final var errorInfoMap = getErrorInfo();
-            if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
-                /*
-                 * <error-info> <bad-attribute>message-id</bad-attribute>
-                 * <bad-element>rpc</bad-element> </error-info>
-                 */
-                final var errorInfoNode = doc.createElementNS(NamespaceURN.BASE, ERROR_INFO);
-                errorInfoNode.setPrefix(rpcReply.getPrefix());
-                rpcError.appendChild(errorInfoNode);
-
-                for (var entry : errorInfoMap.entrySet()) {
-                    errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
-                }
+        final var rpcReply = doc.createElementNS(NamespaceURN.BASE, RPC_REPLY_KEY);
+        doc.appendChild(rpcReply);
+
+        final var rpcError = doc.createElementNS(NamespaceURN.BASE, RPC_ERROR);
+        rpcReply.appendChild(rpcError);
+
+        rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().elementBody()));
+        rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().elementBody()));
+        rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
+        rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
+
+        final var errorInfoMap = getErrorInfo();
+        if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
+            /*
+             * <error-info> <bad-attribute>message-id</bad-attribute>
+             * <bad-element>rpc</bad-element> </error-info>
+             */
+            final var errorInfoNode = doc.createElementNS(NamespaceURN.BASE, ERROR_INFO);
+            errorInfoNode.setPrefix(rpcReply.getPrefix());
+            rpcError.appendChild(errorInfoNode);
+
+            for (var entry : errorInfoMap.entrySet()) {
+                errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
             }
-        } catch (final ParserConfigurationException e) {
-            // this shouldn't happen
-            LOG.error("Error outputting to XML document", e);
         }
 
         return doc;
diff --git a/protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/messages/RpcMessage.java b/protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/messages/RpcMessage.java
new file mode 100644 (file)
index 0000000..01fc13d
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.api.messages;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableMap;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.api.DocumentedException;
+import org.opendaylight.netconf.api.NamespaceURN;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.w3c.dom.Document;
+
+/**
+ * A NETCONF RPC request message, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc6241#section-4.1">RFC6241, section 4.1</a>. Its document element is
+ * guaranteed to be an {@code <rpc/>} in the {@value NamespaceURN#BASE} namespace.
+ */
+public final class RpcMessage extends NetconfMessage {
+    private RpcMessage(final Document document) {
+        super(document);
+    }
+
+    /**
+     * Return an {@link RpcMessage} backed by specified {@link Document}.
+     *
+     * @param document Backing document
+     * @return An {@link RpcMessage}
+     * @throws DocumentedException if the document's structure does not form a valid {@code rpc} message
+     * @throws NullPointerException if {@code document} is {@code null}
+     */
+    public static @NonNull RpcMessage of(final Document document) throws DocumentedException {
+        final var root = document.getDocumentElement();
+        final var rootName = root.getLocalName();
+        if (!XmlNetconfConstants.RPC_KEY.equals(rootName)) {
+            throw new DocumentedException("Unexpected element name " + rootName, ErrorType.PROTOCOL,
+                ErrorTag.UNKNOWN_ELEMENT, ErrorSeverity.ERROR, ImmutableMap.of("bad-element", rootName));
+        }
+        final var rootNs = root.getNamespaceURI();
+        if (!NamespaceURN.BASE.equals(rootNs)) {
+            throw new DocumentedException("Unexpected element namespace " + rootNs, ErrorType.PROTOCOL,
+                ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR, ImmutableMap.of(
+                    "bad-element", rootName,
+                    "bad-namespace", rootNs));
+        }
+
+        final var messageIdAttr = root.getAttributeNode(XmlNetconfConstants.MESSAGE_ID);
+        if (messageIdAttr == null) {
+            throw new DocumentedException("Missing message-id attribute", ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE,
+                ErrorSeverity.ERROR, ImmutableMap.of(
+                    "bad-attribute", XmlNetconfConstants.MESSAGE_ID,
+                    "bad-element", XmlNetconfConstants.RPC_KEY));
+        }
+        if (!messageIdAttr.getSpecified()) {
+            throw new IllegalArgumentException("Document element's message-id attribute is not specified");
+        }
+        return new RpcMessage(document);
+    }
+
+    /**
+     * Return an {@link RpcMessage} wrapping an RPC operation supplied as {@link Document}. The supplied document is
+     * modified to have its document element replaced with a {@code <rpc/>} element which contains it.
+     *
+     * @param document Backing operation document
+     * @return An {@link RpcMessage}
+     * @throws NullPointerException if any argument is {@code null}
+     */
+    public static @NonNull RpcMessage ofOperation(final String messageId, final Document document) {
+        final var rpcElem = document.createElementNS(NamespaceURN.BASE, XmlNetconfConstants.RPC_KEY);
+        rpcElem.appendChild(document.getDocumentElement());
+        rpcElem.setAttribute(XmlNetconfConstants.MESSAGE_ID, requireNonNull(messageId));
+        document.appendChild(rpcElem);
+        return new RpcMessage(document);
+    }
+
+    public @NonNull String messageId() {
+        return getDocument().getDocumentElement().getAttribute(XmlNetconfConstants.MESSAGE_ID);
+    }
+}
diff --git a/protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/messages/RpcReplyMessage.java b/protocol/netconf-api/src/main/java/org/opendaylight/netconf/api/messages/RpcReplyMessage.java
new file mode 100644 (file)
index 0000000..071a3ab
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.api.messages;
+
+import com.google.common.collect.ImmutableMap;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.netconf.api.DocumentedException;
+import org.opendaylight.netconf.api.NamespaceURN;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.w3c.dom.Document;
+
+/**
+ * A NETCONF RPC reply message, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc6241#section-4.2">RFC6241, section 4.2</a>. Its document element is
+ * guaranteed to be an {@code <rpc-reply/>} in the {@value NamespaceURN#BASE} namespace.
+ */
+public final class RpcReplyMessage extends NetconfMessage {
+    private RpcReplyMessage(final Document document) {
+        super(document);
+    }
+
+    /**
+     * Return an {@link RpcReplyMessage} backed by specified {@link Document}.
+     *
+     * @param document Backing document
+     * @return An {@link RpcReplyMessage}
+     * @throws DocumentedException if the document's structure does not form a valid {@code rpc-reply} message
+     * @throws NullPointerException if {@code document} is {@code null}
+     */
+    public static @NonNull RpcReplyMessage of(final Document document) throws DocumentedException {
+        final var root = document.getDocumentElement();
+        final var rootName = root.getLocalName();
+        if (!XmlNetconfConstants.RPC_REPLY_KEY.equals(rootName)) {
+            throw new DocumentedException("Unexpected element name " + rootName, ErrorType.PROTOCOL,
+                ErrorTag.UNKNOWN_ELEMENT, ErrorSeverity.ERROR, ImmutableMap.of("bad-element", rootName));
+        }
+        final var rootNs = root.getNamespaceURI();
+        if (!NamespaceURN.BASE.equals(rootNs)) {
+            throw new DocumentedException("Unexpected element namespace " + rootNs, ErrorType.PROTOCOL,
+                ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR, ImmutableMap.of(
+                    "bad-element", rootName,
+                    "bad-namespace", rootNs));
+        }
+
+        return new RpcReplyMessage(document);
+    }
+
+    /**
+     * Return an {@link RpcReplyMessage} representation.
+     *
+     * @param ex DocumentedException specifying the error
+     * @return An {@link RpcReplyMessage}
+     * @throws NullPointerException if {@code ex} is {@code null}
+     */
+    public static @NonNull RpcReplyMessage of(final DocumentedException ex) {
+        return new RpcReplyMessage(ex.toXMLDocument());
+    }
+
+    public @Nullable String messageId() {
+        final var attr = getDocument().getDocumentElement().getAttributeNode(XmlNetconfConstants.MESSAGE_ID);
+        return attr == null ? null : attr.getValue();
+    }
+}
diff --git a/protocol/netconf-api/src/test/java/org/opendaylight/netconf/api/messages/RpcMessageTest.java b/protocol/netconf-api/src/test/java/org/opendaylight/netconf/api/messages/RpcMessageTest.java
new file mode 100644 (file)
index 0000000..b7b49fb
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.api.messages;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.netconf.api.DocumentedException;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.w3c.dom.Document;
+
+class RpcMessageTest {
+    private final Document document = UntrustedXML.newDocumentBuilder().newDocument();
+
+    @Test
+    void testOf() throws Exception {
+        final var rootElement = document.createElementNS("urn:ietf:params:xml:ns:netconf:base:1.0", "rpc");
+        rootElement.setAttribute("message-id", "foo");
+        document.appendChild(rootElement);
+
+        final var msg = RpcMessage.of(document);
+        assertEquals("foo", msg.messageId());
+        assertEquals("""
+            <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="foo"/>
+            """, msg.toString());
+    }
+
+    @Test
+    void testOfBadElementName() {
+        final var rootElement = document.createElementNS("urn:ietf:params:xml:ns:netconf:base:1.0", "bad");
+        rootElement.setAttribute("message-id", "foo");
+        document.appendChild(rootElement);
+
+        final var ex = assertThrows(DocumentedException.class, () -> RpcMessage.of(document));
+        assertEquals("Unexpected element name bad", ex.getMessage());
+        assertEquals(ErrorSeverity.ERROR, ex.getErrorSeverity());
+        assertEquals(ErrorType.PROTOCOL, ex.getErrorType());
+        assertEquals(ErrorTag.UNKNOWN_ELEMENT, ex.getErrorTag());
+        assertEquals(ImmutableMap.of("bad-element", "bad"), ex.getErrorInfo());
+    }
+
+    @Test
+    void testOfBadElementNs() {
+        final var rootElement = document.createElementNS("bad", "rpc");
+        rootElement.setAttribute("message-id", "foo");
+        document.appendChild(rootElement);
+
+        final var ex = assertThrows(DocumentedException.class, () -> RpcMessage.of(document));
+        assertEquals("Unexpected element namespace bad", ex.getMessage());
+        assertEquals(ErrorSeverity.ERROR, ex.getErrorSeverity());
+        assertEquals(ErrorType.PROTOCOL, ex.getErrorType());
+        assertEquals(ErrorTag.UNKNOWN_NAMESPACE, ex.getErrorTag());
+        assertEquals(ImmutableMap.of("bad-element", "rpc", "bad-namespace", "bad"), ex.getErrorInfo());
+    }
+
+    @Test
+    void testOfMissingMessageId() {
+        document.appendChild(document.createElementNS("urn:ietf:params:xml:ns:netconf:base:1.0", "rpc"));
+
+        final var ex = assertThrows(DocumentedException.class, () -> RpcMessage.of(document));
+        assertEquals("Missing message-id attribute", ex.getMessage());
+        assertEquals(ErrorSeverity.ERROR, ex.getErrorSeverity());
+        assertEquals(ErrorType.RPC, ex.getErrorType());
+        assertEquals(ErrorTag.MISSING_ATTRIBUTE, ex.getErrorTag());
+        assertEquals(ImmutableMap.of("bad-element", "rpc", "bad-attribute", "message-id"), ex.getErrorInfo());
+    }
+
+    @Test
+    void testOfOperation() {
+        final var rootElement = document.createElement("test-root");
+        document.appendChild(rootElement);
+
+        final var msg = RpcMessage.ofOperation("1014", document);
+        assertEquals("1014", msg.messageId());
+        final var msgRoot = msg.getDocument().getDocumentElement();
+        assertEquals("urn:ietf:params:xml:ns:netconf:base:1.0", msgRoot.getNamespaceURI());
+        assertEquals("rpc", msgRoot.getLocalName());
+
+        final var rootList = msgRoot.getChildNodes();
+        assertEquals(1, rootList.getLength());
+        assertSame(rootElement, rootList.item(0));
+        assertEquals("""
+            <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1014">
+                <test-root/>
+            </rpc>
+            """, msg.toString());
+    }
+}
diff --git a/protocol/netconf-api/src/test/java/org/opendaylight/netconf/api/messages/RpcReplyMessageTest.java b/protocol/netconf-api/src/test/java/org/opendaylight/netconf/api/messages/RpcReplyMessageTest.java
new file mode 100644 (file)
index 0000000..2918e7b
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.api.messages;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.netconf.api.DocumentedException;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.w3c.dom.Document;
+
+class RpcReplyMessageTest {
+    private final Document document = UntrustedXML.newDocumentBuilder().newDocument();
+
+    @Test
+    void testOfDocument() throws Exception {
+        final var rootElement = document.createElementNS("urn:ietf:params:xml:ns:netconf:base:1.0", "rpc-reply");
+        rootElement.setAttribute("message-id", "foo");
+        document.appendChild(rootElement);
+
+        final var msg = RpcReplyMessage.of(document);
+        assertEquals("foo", msg.messageId());
+        assertEquals("""
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="foo"/>
+            """, msg.toString());
+    }
+
+    @Test
+    void testOfException() throws Exception {
+        final var msg = RpcReplyMessage.of(new DocumentedException("Missing message-id attribute",
+            ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE, ErrorSeverity.ERROR, ImmutableMap.of(
+                "bad-attribute", XmlNetconfConstants.MESSAGE_ID,
+                "bad-element", XmlNetconfConstants.RPC_KEY)));
+        assertEquals("""
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+                <rpc-error>
+                    <error-type>rpc</error-type>
+                    <error-tag>missing-attribute</error-tag>
+                    <error-severity>error</error-severity>
+                    <error-message>Missing message-id attribute</error-message>
+                    <error-info>
+                        <bad-attribute>message-id</bad-attribute>
+                        <bad-element>rpc</bad-element>
+                    </error-info>
+                </rpc-error>
+            </rpc-reply>
+            """, msg.toString());
+    }
+
+    @Test
+    void testOfBadElementName() {
+        final var rootElement = document.createElementNS("urn:ietf:params:xml:ns:netconf:base:1.0", "bad");
+        rootElement.setAttribute("message-id", "foo");
+        document.appendChild(rootElement);
+
+        final var ex = assertThrows(DocumentedException.class, () -> RpcReplyMessage.of(document));
+        assertEquals("Unexpected element name bad", ex.getMessage());
+        assertEquals(ErrorSeverity.ERROR, ex.getErrorSeverity());
+        assertEquals(ErrorType.PROTOCOL, ex.getErrorType());
+        assertEquals(ErrorTag.UNKNOWN_ELEMENT, ex.getErrorTag());
+        assertEquals(ImmutableMap.of("bad-element", "bad"), ex.getErrorInfo());
+    }
+
+    @Test
+    void testOfBadElementNs() {
+        final var rootElement = document.createElementNS("bad", "rpc-reply");
+        rootElement.setAttribute("message-id", "foo");
+        document.appendChild(rootElement);
+
+        final var ex = assertThrows(DocumentedException.class, () -> RpcReplyMessage.of(document));
+        assertEquals("Unexpected element namespace bad", ex.getMessage());
+        assertEquals(ErrorSeverity.ERROR, ex.getErrorSeverity());
+        assertEquals(ErrorType.PROTOCOL, ex.getErrorType());
+        assertEquals(ErrorTag.UNKNOWN_NAMESPACE, ex.getErrorTag());
+        assertEquals(ImmutableMap.of("bad-element", "rpc-reply", "bad-namespace", "bad"), ex.getErrorInfo());
+    }
+}
index 7598b4baeb5f99cdba99f344100467a160c8500d..86a52bf28265da4f0d118456bea5d414aaae234b 100644 (file)
@@ -58,6 +58,7 @@ public final class SendErrorExceptionUtil {
         channelFuture.addListener(new SendErrorVerifyingListener(sendErrorException));
     }
 
+    // FIXME: this should be handled through RpcMessage.toReply(DocumentedException)
     @SuppressWarnings("checkstyle:IllegalCatch")
     private static void tryToCopyAttributes(final Document incommingDocument, final Document errorDocument,
             final DocumentedException sendErrorException) {