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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.util.HashMap;
16 import java.util.Map.Entry;
17 import javax.xml.parsers.DocumentBuilderFactory;
18 import javax.xml.parsers.ParserConfigurationException;
19 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
20 import org.opendaylight.yangtools.yang.common.ErrorTag;
21 import org.opendaylight.yangtools.yang.common.ErrorType;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.w3c.dom.Document;
25 import org.w3c.dom.Node;
26 import org.w3c.dom.NodeList;
29 * Checked exception to communicate an error that needs to be sent to the
32 // FIXME: NETCONF-793: implement YangNetconfErrorAware
33 public class DocumentedException extends Exception {
35 public static final String RPC_ERROR = "rpc-error";
36 public static final String ERROR_TYPE = "error-type";
37 public static final String ERROR_TAG = "error-tag";
38 public static final String ERROR_SEVERITY = "error-severity";
39 public static final String ERROR_APP_TAG = "error-app-tag";
40 public static final String ERROR_PATH = "error-path";
41 public static final String ERROR_MESSAGE = "error-message";
42 public static final String ERROR_INFO = "error-info";
44 // FIXME: This is an RFC6241 definition, remove it once we have yangtools-7.0.5
45 @Deprecated(forRemoval = true)
46 public static final ErrorTag MALFORMED_MESSAGE = new ErrorTag("malformed-message");
48 private static final long serialVersionUID = 1L;
49 private static final Logger LOG = LoggerFactory.getLogger(DocumentedException.class);
50 private static final DocumentBuilderFactory BUILDER_FACTORY;
53 BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
55 BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
56 BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
57 BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
58 BUILDER_FACTORY.setXIncludeAware(false);
59 BUILDER_FACTORY.setExpandEntityReferences(false);
60 } catch (final ParserConfigurationException e) {
61 throw new ExceptionInInitializerError(e);
63 BUILDER_FACTORY.setNamespaceAware(true);
64 BUILDER_FACTORY.setCoalescing(true);
65 BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
66 BUILDER_FACTORY.setIgnoringComments(true);
69 private final ErrorType errorType;
70 @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "FIXME: should not be necessary with yangtools-7.0.5")
71 private final ErrorTag errorTag;
72 private final ErrorSeverity errorSeverity;
73 private final Map<String, String> errorInfo;
75 public DocumentedException(final String message) {
76 this(message, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
79 public DocumentedException(final String message, final Exception cause) {
80 this(message, cause, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
83 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
84 final ErrorSeverity errorSeverity) {
85 this(message, errorType, errorTag, errorSeverity, Map.of());
88 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
89 final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
91 this.errorType = errorType;
92 this.errorTag = errorTag;
93 this.errorSeverity = errorSeverity;
94 this.errorInfo = errorInfo;
97 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
98 final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
99 this(message, cause, errorType, errorTag, errorSeverity, Map.of());
102 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
103 final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
104 super(message, cause);
105 this.errorType = errorType;
106 this.errorTag = errorTag;
107 this.errorSeverity = errorSeverity;
108 // FIXME: this contract (based on what fromXMLDocument does) is quite wrong. It ignores the XML realities of
109 // what constitutes a tag and especially tag value when faced with encoding XML-namespaced entities --
110 // such as 'identity' arguments -- represented as QNames.
111 this.errorInfo = errorInfo;
114 public static DocumentedException wrap(final Exception exception) throws DocumentedException {
115 throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION,
116 ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR,
117 Map.of(ErrorTag.OPERATION_FAILED.elementBody(), "Exception thrown"));
120 public static DocumentedException fromXMLDocument(final Document fromDoc) {
122 ErrorType errorType = ErrorType.APPLICATION;
123 ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
124 ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
125 Map<String, String> errorInfo = null;
126 String errorMessage = "";
127 String allErrorMessages = "";
129 Node rpcReply = fromDoc.getDocumentElement();
131 // FIXME: we only handle one rpc-error. For now, shove extra errorMessages found in multiple rpc-error in the
132 // errorInfo Map to at least let them propagate back to caller.
133 // this will be solved through migration to YangNetconfErrorAware, as that allows reporting multipl
135 int rpcErrorCount = 0;
137 NodeList replyChildren = rpcReply.getChildNodes();
138 for (int i = 0; i < replyChildren.getLength(); i++) {
139 Node replyChild = replyChildren.item(i);
140 if (RPC_ERROR.equals(replyChild.getLocalName())) {
142 NodeList rpcErrorChildren = replyChild.getChildNodes();
143 for (int j = 0; j < rpcErrorChildren.getLength(); j++) {
144 Node rpcErrorChild = rpcErrorChildren.item(j);
146 // FIXME: use a switch expression here
147 if (ERROR_TYPE.equals(rpcErrorChild.getLocalName())) {
148 final ErrorType type = ErrorType.forElementBody(rpcErrorChild.getTextContent());
149 // FIXME: this should be a hard error
150 errorType = type != null ? type : ErrorType.APPLICATION;
151 } else if (ERROR_TAG.equals(rpcErrorChild.getLocalName())) {
152 errorTag = new ErrorTag(rpcErrorChild.getTextContent());
153 } else if (ERROR_SEVERITY.equals(rpcErrorChild.getLocalName())) {
154 final ErrorSeverity sev = ErrorSeverity.forElementBody(rpcErrorChild.getTextContent());
155 // FIXME: this should be a hard error
156 errorSeverity = sev != null ? sev : ErrorSeverity.ERROR;
157 } else if (ERROR_MESSAGE.equals(rpcErrorChild.getLocalName())) {
158 errorMessage = rpcErrorChild.getTextContent();
159 allErrorMessages = allErrorMessages + errorMessage;
160 } else if (ERROR_INFO.equals(rpcErrorChild.getLocalName())) {
161 errorInfo = parseErrorInfo(rpcErrorChild);
167 if (rpcErrorCount > 1) {
168 if (errorInfo == null) {
169 errorInfo = new HashMap<>();
171 errorInfo.put("Multiple Errors Found", allErrorMessages);
174 return new DocumentedException(errorMessage, errorType, errorTag, errorSeverity, errorInfo);
177 private static Map<String, String> parseErrorInfo(final Node node) {
178 Map<String, String> infoMap = new HashMap<>();
179 NodeList children = node.getChildNodes();
180 for (int i = 0; i < children.getLength(); i++) {
181 Node child = children.item(i);
182 if (child.getNodeType() == Node.ELEMENT_NODE) {
183 // FIXME: Holy namespace ignorance, Batman!
185 // So this is just not enough to decode things in the general sense. getTextContenxt() may easily be a
186 // qualified QName, such as an identity name or an instance-identifier. What the entire 'infoMap' needs
187 // to contain is each child's XML context, so that the string literal can be interpreted as needed.
189 // yang.common.YangNamespaceContext represents the minimal API surface that needs to be exposed. That
190 // effectively means:
192 // final class ElementValue implements YangNamespaceContext {
193 // public final String elementContenxt();
196 // Map<QName, ElementValue> infoMap;
198 // except... what do we use for revision?
199 infoMap.put(child.getNodeName(), child.getTextContent());
206 // FIXME: NETCONF-793: remove all of these in favor of YangNetconfErrorAware
207 public ErrorType getErrorType() {
208 return this.errorType;
211 public ErrorTag getErrorTag() {
212 return this.errorTag;
215 public ErrorSeverity getErrorSeverity() {
216 return this.errorSeverity;
219 public Map<String, String> getErrorInfo() {
220 return this.errorInfo;
223 // FIXME: this really should be an spi/util method (or even netconf-util-w3c-dom?) as this certainly is not the
224 // primary interface we want to expose -- it is inherently mutable and its API is a pure nightmare.
225 public Document toXMLDocument() {
228 doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
230 Node rpcReply = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
231 doc.appendChild(rpcReply);
233 Node rpcError = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR);
234 rpcReply.appendChild(rpcError);
236 rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().elementBody()));
237 rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().elementBody()));
238 rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
239 rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
241 Map<String, String> errorInfoMap = getErrorInfo();
242 if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
244 * <error-info> <bad-attribute>message-id</bad-attribute>
245 * <bad-element>rpc</bad-element> </error-info>
248 Node errorInfoNode = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO);
249 errorInfoNode.setPrefix(rpcReply.getPrefix());
250 rpcError.appendChild(errorInfoNode);
252 for (Entry<String, String> entry : errorInfoMap.entrySet()) {
253 errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
256 } catch (final ParserConfigurationException e) {
257 // this shouldn't happen
258 LOG.error("Error outputting to XML document", e);
264 private static Node createTextNode(final Document doc, final String tag, final String textContent) {
265 Node node = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag);
266 node.setTextContent(textContent);
271 public String toString() {
272 return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
273 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
274 + this.errorInfo + '}';