2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.api;
10 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY;
12 import java.util.HashMap;
14 import java.util.Map.Entry;
15 import javax.xml.parsers.DocumentBuilderFactory;
16 import javax.xml.parsers.ParserConfigurationException;
17 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
18 import org.opendaylight.yangtools.yang.common.ErrorTag;
19 import org.opendaylight.yangtools.yang.common.ErrorType;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import org.w3c.dom.Document;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
27 * Checked exception to communicate an error that needs to be sent to the
30 // FIXME: NETCONF-793: implement YangNetconfErrorAware
31 public class DocumentedException extends Exception {
33 public static final String RPC_ERROR = "rpc-error";
34 public static final String ERROR_TYPE = "error-type";
35 public static final String ERROR_TAG = "error-tag";
36 public static final String ERROR_SEVERITY = "error-severity";
37 public static final String ERROR_APP_TAG = "error-app-tag";
38 public static final String ERROR_PATH = "error-path";
39 public static final String ERROR_MESSAGE = "error-message";
40 public static final String ERROR_INFO = "error-info";
42 private static final long serialVersionUID = 1L;
43 private static final Logger LOG = LoggerFactory.getLogger(DocumentedException.class);
44 private static final DocumentBuilderFactory BUILDER_FACTORY;
47 BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
49 BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
50 BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
51 BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
52 BUILDER_FACTORY.setXIncludeAware(false);
53 BUILDER_FACTORY.setExpandEntityReferences(false);
54 } catch (final ParserConfigurationException e) {
55 throw new ExceptionInInitializerError(e);
57 BUILDER_FACTORY.setNamespaceAware(true);
58 BUILDER_FACTORY.setCoalescing(true);
59 BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
60 BUILDER_FACTORY.setIgnoringComments(true);
63 private final ErrorType errorType;
64 private final ErrorTag errorTag;
65 private final ErrorSeverity errorSeverity;
66 private final Map<String, String> errorInfo;
68 public DocumentedException(final String message) {
69 this(message, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
72 public DocumentedException(final String message, final Exception cause) {
73 this(message, cause, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
76 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
77 final ErrorSeverity errorSeverity) {
78 this(message, errorType, errorTag, errorSeverity, Map.of());
81 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
82 final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
84 this.errorType = errorType;
85 this.errorTag = errorTag;
86 this.errorSeverity = errorSeverity;
87 this.errorInfo = errorInfo;
90 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
91 final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
92 this(message, cause, errorType, errorTag, errorSeverity, Map.of());
95 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
96 final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
97 super(message, cause);
98 this.errorType = errorType;
99 this.errorTag = errorTag;
100 this.errorSeverity = errorSeverity;
101 // FIXME: this contract (based on what fromXMLDocument does) is quite wrong. It ignores the XML realities of
102 // what constitutes a tag and especially tag value when faced with encoding XML-namespaced entities --
103 // such as 'identity' arguments -- represented as QNames.
104 this.errorInfo = errorInfo;
107 public static DocumentedException wrap(final Exception exception) throws DocumentedException {
108 throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION,
109 ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR,
110 Map.of(ErrorTag.OPERATION_FAILED.elementBody(), "Exception thrown"));
113 public static DocumentedException fromXMLDocument(final Document fromDoc) {
115 ErrorType errorType = ErrorType.APPLICATION;
116 ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
117 ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
118 Map<String, String> errorInfo = null;
119 String errorMessage = "";
120 StringBuilder allErrorMessages = new StringBuilder();
122 Node rpcReply = fromDoc.getDocumentElement();
124 // FIXME: we only handle one rpc-error. For now, shove extra errorMessages found in multiple rpc-error in the
125 // errorInfo Map to at least let them propagate back to caller.
126 // this will be solved through migration to YangNetconfErrorAware, as that allows reporting multipl
128 int rpcErrorCount = 0;
130 NodeList replyChildren = rpcReply.getChildNodes();
131 for (int i = 0; i < replyChildren.getLength(); i++) {
132 Node replyChild = replyChildren.item(i);
133 if (RPC_ERROR.equals(replyChild.getLocalName())) {
135 NodeList rpcErrorChildren = replyChild.getChildNodes();
136 for (int j = 0; j < rpcErrorChildren.getLength(); j++) {
137 Node rpcErrorChild = rpcErrorChildren.item(j);
139 // FIXME: use a switch expression here
140 if (ERROR_TYPE.equals(rpcErrorChild.getLocalName())) {
141 final ErrorType type = ErrorType.forElementBody(rpcErrorChild.getTextContent());
142 // FIXME: this should be a hard error
143 errorType = type != null ? type : ErrorType.APPLICATION;
144 } else if (ERROR_TAG.equals(rpcErrorChild.getLocalName())) {
145 errorTag = new ErrorTag(rpcErrorChild.getTextContent());
146 } else if (ERROR_SEVERITY.equals(rpcErrorChild.getLocalName())) {
147 final ErrorSeverity sev = ErrorSeverity.forElementBody(rpcErrorChild.getTextContent());
148 // FIXME: this should be a hard error
149 errorSeverity = sev != null ? sev : ErrorSeverity.ERROR;
150 } else if (ERROR_MESSAGE.equals(rpcErrorChild.getLocalName())) {
151 errorMessage = rpcErrorChild.getTextContent();
152 allErrorMessages.append(errorMessage);
153 } else if (ERROR_INFO.equals(rpcErrorChild.getLocalName())) {
154 errorInfo = parseErrorInfo(rpcErrorChild);
160 if (rpcErrorCount > 1) {
161 if (errorInfo == null) {
162 errorInfo = new HashMap<>();
164 errorInfo.put("Multiple Errors Found", allErrorMessages.toString());
167 return new DocumentedException(errorMessage, errorType, errorTag, errorSeverity, errorInfo);
170 private static Map<String, String> parseErrorInfo(final Node node) {
171 Map<String, String> infoMap = new HashMap<>();
172 NodeList children = node.getChildNodes();
173 for (int i = 0; i < children.getLength(); i++) {
174 Node child = children.item(i);
175 if (child.getNodeType() == Node.ELEMENT_NODE) {
176 // FIXME: Holy namespace ignorance, Batman!
178 // So this is just not enough to decode things in the general sense. getTextContenxt() may easily be a
179 // qualified QName, such as an identity name or an instance-identifier. What the entire 'infoMap' needs
180 // to contain is each child's XML context, so that the string literal can be interpreted as needed.
182 // yang.common.YangNamespaceContext represents the minimal API surface that needs to be exposed. That
183 // effectively means:
185 // final class ElementValue implements YangNamespaceContext {
186 // public final String elementContenxt();
189 // Map<QName, ElementValue> infoMap;
191 // except... what do we use for revision?
192 infoMap.put(child.getNodeName(), child.getTextContent());
199 // FIXME: NETCONF-793: remove all of these in favor of YangNetconfErrorAware
200 public ErrorType getErrorType() {
204 public ErrorTag getErrorTag() {
208 public ErrorSeverity getErrorSeverity() {
209 return errorSeverity;
212 public Map<String, String> getErrorInfo() {
216 // FIXME: this really should be an spi/util method (or even netconf-util-w3c-dom?) as this certainly is not the
217 // primary interface we want to expose -- it is inherently mutable and its API is a pure nightmare.
218 public Document toXMLDocument() {
221 doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
223 Node rpcReply = doc.createElementNS(NamespaceURN.BASE, RPC_REPLY_KEY);
224 doc.appendChild(rpcReply);
226 Node rpcError = doc.createElementNS(NamespaceURN.BASE, RPC_ERROR);
227 rpcReply.appendChild(rpcError);
229 rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().elementBody()));
230 rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().elementBody()));
231 rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
232 rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
234 Map<String, String> errorInfoMap = getErrorInfo();
235 if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
237 * <error-info> <bad-attribute>message-id</bad-attribute>
238 * <bad-element>rpc</bad-element> </error-info>
241 Node errorInfoNode = doc.createElementNS(NamespaceURN.BASE, ERROR_INFO);
242 errorInfoNode.setPrefix(rpcReply.getPrefix());
243 rpcError.appendChild(errorInfoNode);
245 for (Entry<String, String> entry : errorInfoMap.entrySet()) {
246 errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
249 } catch (final ParserConfigurationException e) {
250 // this shouldn't happen
251 LOG.error("Error outputting to XML document", e);
257 private static Node createTextNode(final Document doc, final String tag, final String textContent) {
258 Node node = doc.createElementNS(NamespaceURN.BASE, tag);
259 node.setTextContent(textContent);
264 public String toString() {
265 return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + errorType
266 + ", errorTag=" + errorTag + ", errorSeverity=" + errorSeverity + ", errorInfo="