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;
11 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
13 import java.util.HashMap;
15 import java.util.Map.Entry;
16 import javax.xml.parsers.DocumentBuilderFactory;
17 import javax.xml.parsers.ParserConfigurationException;
18 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
19 import org.opendaylight.yangtools.yang.common.ErrorTag;
20 import org.opendaylight.yangtools.yang.common.ErrorType;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23 import org.w3c.dom.Document;
24 import org.w3c.dom.Node;
25 import org.w3c.dom.NodeList;
28 * Checked exception to communicate an error that needs to be sent to the
31 // FIXME: NETCONF-793: implement YangNetconfErrorAware
32 public class DocumentedException extends Exception {
34 public static final String RPC_ERROR = "rpc-error";
35 public static final String ERROR_TYPE = "error-type";
36 public static final String ERROR_TAG = "error-tag";
37 public static final String ERROR_SEVERITY = "error-severity";
38 public static final String ERROR_APP_TAG = "error-app-tag";
39 public static final String ERROR_PATH = "error-path";
40 public static final String ERROR_MESSAGE = "error-message";
41 public static final String ERROR_INFO = "error-info";
43 private static final long serialVersionUID = 1L;
44 private static final Logger LOG = LoggerFactory.getLogger(DocumentedException.class);
45 private static final DocumentBuilderFactory BUILDER_FACTORY;
48 BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
50 BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
51 BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
52 BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
53 BUILDER_FACTORY.setXIncludeAware(false);
54 BUILDER_FACTORY.setExpandEntityReferences(false);
55 } catch (final ParserConfigurationException e) {
56 throw new ExceptionInInitializerError(e);
58 BUILDER_FACTORY.setNamespaceAware(true);
59 BUILDER_FACTORY.setCoalescing(true);
60 BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
61 BUILDER_FACTORY.setIgnoringComments(true);
64 private final ErrorType errorType;
65 private final ErrorTag errorTag;
66 private final ErrorSeverity errorSeverity;
67 private final Map<String, String> errorInfo;
69 public DocumentedException(final String message) {
70 this(message, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
73 public DocumentedException(final String message, final Exception cause) {
74 this(message, cause, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
77 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
78 final ErrorSeverity errorSeverity) {
79 this(message, errorType, errorTag, errorSeverity, Map.of());
82 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
83 final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
85 this.errorType = errorType;
86 this.errorTag = errorTag;
87 this.errorSeverity = errorSeverity;
88 this.errorInfo = errorInfo;
91 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
92 final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
93 this(message, cause, errorType, errorTag, errorSeverity, Map.of());
96 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
97 final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
98 super(message, cause);
99 this.errorType = errorType;
100 this.errorTag = errorTag;
101 this.errorSeverity = errorSeverity;
102 // FIXME: this contract (based on what fromXMLDocument does) is quite wrong. It ignores the XML realities of
103 // what constitutes a tag and especially tag value when faced with encoding XML-namespaced entities --
104 // such as 'identity' arguments -- represented as QNames.
105 this.errorInfo = errorInfo;
108 public static DocumentedException wrap(final Exception exception) throws DocumentedException {
109 throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION,
110 ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR,
111 Map.of(ErrorTag.OPERATION_FAILED.elementBody(), "Exception thrown"));
114 public static DocumentedException fromXMLDocument(final Document fromDoc) {
116 ErrorType errorType = ErrorType.APPLICATION;
117 ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
118 ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
119 Map<String, String> errorInfo = null;
120 String errorMessage = "";
121 StringBuilder allErrorMessages = new StringBuilder();
123 Node rpcReply = fromDoc.getDocumentElement();
125 // FIXME: we only handle one rpc-error. For now, shove extra errorMessages found in multiple rpc-error in the
126 // errorInfo Map to at least let them propagate back to caller.
127 // this will be solved through migration to YangNetconfErrorAware, as that allows reporting multipl
129 int rpcErrorCount = 0;
131 NodeList replyChildren = rpcReply.getChildNodes();
132 for (int i = 0; i < replyChildren.getLength(); i++) {
133 Node replyChild = replyChildren.item(i);
134 if (RPC_ERROR.equals(replyChild.getLocalName())) {
136 NodeList rpcErrorChildren = replyChild.getChildNodes();
137 for (int j = 0; j < rpcErrorChildren.getLength(); j++) {
138 Node rpcErrorChild = rpcErrorChildren.item(j);
140 // FIXME: use a switch expression here
141 if (ERROR_TYPE.equals(rpcErrorChild.getLocalName())) {
142 final ErrorType type = ErrorType.forElementBody(rpcErrorChild.getTextContent());
143 // FIXME: this should be a hard error
144 errorType = type != null ? type : ErrorType.APPLICATION;
145 } else if (ERROR_TAG.equals(rpcErrorChild.getLocalName())) {
146 errorTag = new ErrorTag(rpcErrorChild.getTextContent());
147 } else if (ERROR_SEVERITY.equals(rpcErrorChild.getLocalName())) {
148 final ErrorSeverity sev = ErrorSeverity.forElementBody(rpcErrorChild.getTextContent());
149 // FIXME: this should be a hard error
150 errorSeverity = sev != null ? sev : ErrorSeverity.ERROR;
151 } else if (ERROR_MESSAGE.equals(rpcErrorChild.getLocalName())) {
152 errorMessage = rpcErrorChild.getTextContent();
153 allErrorMessages.append(errorMessage);
154 } else if (ERROR_INFO.equals(rpcErrorChild.getLocalName())) {
155 errorInfo = parseErrorInfo(rpcErrorChild);
161 if (rpcErrorCount > 1) {
162 if (errorInfo == null) {
163 errorInfo = new HashMap<>();
165 errorInfo.put("Multiple Errors Found", allErrorMessages.toString());
168 return new DocumentedException(errorMessage, errorType, errorTag, errorSeverity, errorInfo);
171 private static Map<String, String> parseErrorInfo(final Node node) {
172 Map<String, String> infoMap = new HashMap<>();
173 NodeList children = node.getChildNodes();
174 for (int i = 0; i < children.getLength(); i++) {
175 Node child = children.item(i);
176 if (child.getNodeType() == Node.ELEMENT_NODE) {
177 // FIXME: Holy namespace ignorance, Batman!
179 // So this is just not enough to decode things in the general sense. getTextContenxt() may easily be a
180 // qualified QName, such as an identity name or an instance-identifier. What the entire 'infoMap' needs
181 // to contain is each child's XML context, so that the string literal can be interpreted as needed.
183 // yang.common.YangNamespaceContext represents the minimal API surface that needs to be exposed. That
184 // effectively means:
186 // final class ElementValue implements YangNamespaceContext {
187 // public final String elementContenxt();
190 // Map<QName, ElementValue> infoMap;
192 // except... what do we use for revision?
193 infoMap.put(child.getNodeName(), child.getTextContent());
200 // FIXME: NETCONF-793: remove all of these in favor of YangNetconfErrorAware
201 public ErrorType getErrorType() {
202 return this.errorType;
205 public ErrorTag getErrorTag() {
206 return this.errorTag;
209 public ErrorSeverity getErrorSeverity() {
210 return this.errorSeverity;
213 public Map<String, String> getErrorInfo() {
214 return this.errorInfo;
217 // FIXME: this really should be an spi/util method (or even netconf-util-w3c-dom?) as this certainly is not the
218 // primary interface we want to expose -- it is inherently mutable and its API is a pure nightmare.
219 public Document toXMLDocument() {
222 doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
224 Node rpcReply = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
225 doc.appendChild(rpcReply);
227 Node rpcError = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR);
228 rpcReply.appendChild(rpcError);
230 rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().elementBody()));
231 rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().elementBody()));
232 rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
233 rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
235 Map<String, String> errorInfoMap = getErrorInfo();
236 if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
238 * <error-info> <bad-attribute>message-id</bad-attribute>
239 * <bad-element>rpc</bad-element> </error-info>
242 Node errorInfoNode = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO);
243 errorInfoNode.setPrefix(rpcReply.getPrefix());
244 rpcError.appendChild(errorInfoNode);
246 for (Entry<String, String> entry : errorInfoMap.entrySet()) {
247 errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
250 } catch (final ParserConfigurationException e) {
251 // this shouldn't happen
252 LOG.error("Error outputting to XML document", e);
258 private static Node createTextNode(final Document doc, final String tag, final String textContent) {
259 Node node = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag);
260 node.setTextContent(textContent);
265 public String toString() {
266 return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
267 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
268 + this.errorInfo + '}';