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 java.util.Objects.requireNonNull;
11 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY;
12 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
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.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 public class DocumentedException extends Exception {
32 public static final String RPC_ERROR = "rpc-error";
33 public static final String ERROR_TYPE = "error-type";
34 public static final String ERROR_TAG = "error-tag";
35 public static final String ERROR_SEVERITY = "error-severity";
36 public static final String ERROR_APP_TAG = "error-app-tag";
37 public static final String ERROR_PATH = "error-path";
38 public static final String ERROR_MESSAGE = "error-message";
39 public static final String ERROR_INFO = "error-info";
41 private static final long serialVersionUID = 1L;
43 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 public enum ErrorType {
65 TRANSPORT("transport"), RPC("rpc"), PROTOCOL("protocol"), APPLICATION("application");
67 private final String typeValue;
69 ErrorType(final String typeValue) {
70 this.typeValue = requireNonNull(typeValue);
73 public String getTypeValue() {
74 return this.typeValue;
77 public static ErrorType from(final String text) {
78 for (ErrorType e : values()) {
79 if (e.getTypeValue().equalsIgnoreCase(text)) {
88 public enum ErrorTag {
89 ACCESS_DENIED("access-denied"),
90 BAD_ATTRIBUTE("bad-attribute"),
91 BAD_ELEMENT("bad-element"),
92 DATA_EXISTS("data-exists"),
93 DATA_MISSING("data-missing"),
95 INVALID_VALUE("invalid-value"),
96 LOCK_DENIED("lock-denied"),
97 MALFORMED_MESSAGE("malformed-message"),
98 MISSING_ATTRIBUTE("missing-attribute"),
99 MISSING_ELEMENT("missing-element"),
100 OPERATION_FAILED("operation-failed"),
101 OPERATION_NOT_SUPPORTED("operation-not-supported"),
102 RESOURCE_DENIED("resource-denied"),
103 ROLLBCK_FAILED("rollback-failed"),
105 UNKNOWN_ATTRIBUTE("unknown-attribute"),
106 UNKNOWN_ELEMENT("unknown-element"),
107 UNKNOWN_NAMESPACE("unknown-namespace");
109 private final String tagValue;
111 ErrorTag(final String tagValue) {
112 this.tagValue = tagValue;
115 public String getTagValue() {
116 return this.tagValue;
119 public static ErrorTag from(final String text) {
120 for (ErrorTag e : values()) {
121 if (e.getTagValue().equals(text)) {
126 return OPERATION_FAILED;
130 private final ErrorType errorType;
131 private final ErrorTag errorTag;
132 private final ErrorSeverity errorSeverity;
133 private final Map<String, String> errorInfo;
135 public DocumentedException(final String message) {
136 this(message, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
139 public DocumentedException(final String message, final Exception cause) {
140 this(message, cause, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
143 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
144 final ErrorSeverity errorSeverity) {
145 this(message, errorType, errorTag, errorSeverity, Map.of());
148 public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
149 final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
151 this.errorType = errorType;
152 this.errorTag = errorTag;
153 this.errorSeverity = errorSeverity;
154 this.errorInfo = errorInfo;
157 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
158 final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
159 this(message, cause, errorType, errorTag, errorSeverity, Map.of());
162 public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
163 final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
164 super(message, cause);
165 this.errorType = errorType;
166 this.errorTag = errorTag;
167 this.errorSeverity = errorSeverity;
168 this.errorInfo = errorInfo;
171 public static <E extends Exception> DocumentedException wrap(final E exception) throws DocumentedException {
172 final Map<String, String> errorInfo = new HashMap<>();
173 errorInfo.put(ErrorTag.OPERATION_FAILED.name(), "Exception thrown");
174 throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION,
175 ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR, errorInfo);
178 public static DocumentedException fromXMLDocument(final Document fromDoc) {
180 ErrorType errorType = ErrorType.APPLICATION;
181 ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
182 ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
183 Map<String, String> errorInfo = null;
184 String errorMessage = "";
185 String allErrorMessages = "";
187 Node rpcReply = fromDoc.getDocumentElement();
189 // FIXME: BUG? - we only handle one rpc-error. For now, shove extra errorMessages
190 // found in multiple rpc-error in the errorInfo Map to at least let them propagate
192 int rpcErrorCount = 0;
194 NodeList replyChildren = rpcReply.getChildNodes();
195 for (int i = 0; i < replyChildren.getLength(); i++) {
196 Node replyChild = replyChildren.item(i);
197 if (RPC_ERROR.equals(replyChild.getLocalName())) {
199 NodeList rpcErrorChildren = replyChild.getChildNodes();
200 for (int j = 0; j < rpcErrorChildren.getLength(); j++) {
201 Node rpcErrorChild = rpcErrorChildren.item(j);
203 // FIXME: use a switch expression here
204 if (ERROR_TYPE.equals(rpcErrorChild.getLocalName())) {
205 errorType = ErrorType.from(rpcErrorChild.getTextContent());
206 } else if (ERROR_TAG.equals(rpcErrorChild.getLocalName())) {
207 errorTag = ErrorTag.from(rpcErrorChild.getTextContent());
208 } else if (ERROR_SEVERITY.equals(rpcErrorChild.getLocalName())) {
209 final ErrorSeverity sev = ErrorSeverity.forElementBody(rpcErrorChild.getTextContent());
210 // FIXME: this should be a hard error
211 errorSeverity = sev != null ? sev : ErrorSeverity.ERROR;
212 } else if (ERROR_MESSAGE.equals(rpcErrorChild.getLocalName())) {
213 errorMessage = rpcErrorChild.getTextContent();
214 allErrorMessages = allErrorMessages + errorMessage;
215 } else if (ERROR_INFO.equals(rpcErrorChild.getLocalName())) {
216 errorInfo = parseErrorInfo(rpcErrorChild);
222 if (rpcErrorCount > 1) {
223 if (errorInfo == null) {
224 errorInfo = new HashMap<>();
226 errorInfo.put("Multiple Errors Found", allErrorMessages);
229 return new DocumentedException(errorMessage, errorType, errorTag, errorSeverity, errorInfo);
232 private static Map<String, String> parseErrorInfo(final Node node) {
233 Map<String, String> infoMap = new HashMap<>();
234 NodeList children = node.getChildNodes();
235 for (int i = 0; i < children.getLength(); i++) {
236 Node child = children.item(i);
237 if (child.getNodeType() == Node.ELEMENT_NODE) {
238 infoMap.put(child.getNodeName(), child.getTextContent());
245 public ErrorType getErrorType() {
246 return this.errorType;
249 public ErrorTag getErrorTag() {
250 return this.errorTag;
253 public ErrorSeverity getErrorSeverity() {
254 return this.errorSeverity;
257 public Map<String, String> getErrorInfo() {
258 return this.errorInfo;
261 public Document toXMLDocument() {
264 doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
266 Node rpcReply = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
267 doc.appendChild(rpcReply);
269 Node rpcError = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR);
270 rpcReply.appendChild(rpcError);
272 rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().getTypeValue()));
273 rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().getTagValue()));
274 rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
275 rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
277 Map<String, String> errorInfoMap = getErrorInfo();
278 if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
280 * <error-info> <bad-attribute>message-id</bad-attribute>
281 * <bad-element>rpc</bad-element> </error-info>
284 Node errorInfoNode = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO);
285 errorInfoNode.setPrefix(rpcReply.getPrefix());
286 rpcError.appendChild(errorInfoNode);
288 for (Entry<String, String> entry : errorInfoMap.entrySet()) {
289 errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
292 } catch (final ParserConfigurationException e) {
293 // this shouldn't happen
294 LOG.error("Error outputting to XML document", e);
300 private Node createTextNode(final Document doc, final String tag, final String textContent) {
301 Node node = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag);
302 node.setTextContent(textContent);
307 public String toString() {
308 return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
309 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
310 + this.errorInfo + '}';