Remove DocumentedException.ErrorType
[netconf.git] / netconf / netconf-api / src / main / java / org / opendaylight / netconf / api / DocumentedException.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.api;
9
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;
12
13 import java.util.HashMap;
14 import java.util.Map;
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.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;
25
26 /**
27  * Checked exception to communicate an error that needs to be sent to the
28  * netconf client.
29  */
30 public class DocumentedException extends Exception {
31
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";
40
41     private static final long serialVersionUID = 1L;
42
43     private static final Logger LOG = LoggerFactory.getLogger(DocumentedException.class);
44
45     private static final DocumentBuilderFactory BUILDER_FACTORY;
46
47     static {
48         BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
49         try {
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);
57         }
58         BUILDER_FACTORY.setNamespaceAware(true);
59         BUILDER_FACTORY.setCoalescing(true);
60         BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
61         BUILDER_FACTORY.setIgnoringComments(true);
62     }
63
64     public enum ErrorTag {
65         ACCESS_DENIED("access-denied"),
66         BAD_ATTRIBUTE("bad-attribute"),
67         BAD_ELEMENT("bad-element"),
68         DATA_EXISTS("data-exists"),
69         DATA_MISSING("data-missing"),
70         IN_USE("in-use"),
71         INVALID_VALUE("invalid-value"),
72         LOCK_DENIED("lock-denied"),
73         MALFORMED_MESSAGE("malformed-message"),
74         MISSING_ATTRIBUTE("missing-attribute"),
75         MISSING_ELEMENT("missing-element"),
76         OPERATION_FAILED("operation-failed"),
77         OPERATION_NOT_SUPPORTED("operation-not-supported"),
78         RESOURCE_DENIED("resource-denied"),
79         ROLLBCK_FAILED("rollback-failed"),
80         TOO_BIG("too-big"),
81         UNKNOWN_ATTRIBUTE("unknown-attribute"),
82         UNKNOWN_ELEMENT("unknown-element"),
83         UNKNOWN_NAMESPACE("unknown-namespace");
84
85         private final String tagValue;
86
87         ErrorTag(final String tagValue) {
88             this.tagValue = tagValue;
89         }
90
91         public String getTagValue() {
92             return this.tagValue;
93         }
94
95         public static ErrorTag from(final String text) {
96             for (ErrorTag e : values()) {
97                 if (e.getTagValue().equals(text)) {
98                     return e;
99                 }
100             }
101
102             return OPERATION_FAILED;
103         }
104     }
105
106     private final ErrorType errorType;
107     private final ErrorTag errorTag;
108     private final ErrorSeverity errorSeverity;
109     private final Map<String, String> errorInfo;
110
111     public DocumentedException(final String message) {
112         this(message, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
113     }
114
115     public DocumentedException(final String message, final Exception cause) {
116         this(message, cause, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
117     }
118
119     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
120             final ErrorSeverity errorSeverity) {
121         this(message, errorType, errorTag, errorSeverity, Map.of());
122     }
123
124     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
125             final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
126         super(message);
127         this.errorType = errorType;
128         this.errorTag = errorTag;
129         this.errorSeverity = errorSeverity;
130         this.errorInfo = errorInfo;
131     }
132
133     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
134             final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
135         this(message, cause, errorType, errorTag, errorSeverity, Map.of());
136     }
137
138     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
139             final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
140         super(message, cause);
141         this.errorType = errorType;
142         this.errorTag = errorTag;
143         this.errorSeverity = errorSeverity;
144         this.errorInfo = errorInfo;
145     }
146
147     public static <E extends Exception> DocumentedException wrap(final E exception) throws DocumentedException {
148         final Map<String, String> errorInfo = new HashMap<>();
149         errorInfo.put(ErrorTag.OPERATION_FAILED.name(), "Exception thrown");
150         throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION,
151                 ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR, errorInfo);
152     }
153
154     public static DocumentedException fromXMLDocument(final Document fromDoc) {
155
156         ErrorType errorType = ErrorType.APPLICATION;
157         ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
158         ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
159         Map<String, String> errorInfo = null;
160         String errorMessage = "";
161         String allErrorMessages = "";
162
163         Node rpcReply = fromDoc.getDocumentElement();
164
165         // FIXME: BUG? - we only handle one rpc-error. For now, shove extra errorMessages
166         // found in multiple rpc-error in the errorInfo Map to at least let them propagate
167         // back to caller.
168         int rpcErrorCount = 0;
169
170         NodeList replyChildren = rpcReply.getChildNodes();
171         for (int i = 0; i < replyChildren.getLength(); i++) {
172             Node replyChild = replyChildren.item(i);
173             if (RPC_ERROR.equals(replyChild.getLocalName())) {
174                 rpcErrorCount++;
175                 NodeList rpcErrorChildren = replyChild.getChildNodes();
176                 for (int j = 0; j < rpcErrorChildren.getLength(); j++) {
177                     Node rpcErrorChild = rpcErrorChildren.item(j);
178
179                     // FIXME: use a switch expression here
180                     if (ERROR_TYPE.equals(rpcErrorChild.getLocalName())) {
181                         final ErrorType type = ErrorType.forElementBody(rpcErrorChild.getTextContent());
182                         // FIXME: this should be a hard error
183                         errorType = type != null ? type : ErrorType.APPLICATION;
184                     } else if (ERROR_TAG.equals(rpcErrorChild.getLocalName())) {
185                         errorTag = ErrorTag.from(rpcErrorChild.getTextContent());
186                     } else if (ERROR_SEVERITY.equals(rpcErrorChild.getLocalName())) {
187                         final ErrorSeverity sev = ErrorSeverity.forElementBody(rpcErrorChild.getTextContent());
188                         // FIXME: this should be a hard error
189                         errorSeverity = sev != null ? sev : ErrorSeverity.ERROR;
190                     } else if (ERROR_MESSAGE.equals(rpcErrorChild.getLocalName())) {
191                         errorMessage = rpcErrorChild.getTextContent();
192                         allErrorMessages = allErrorMessages + errorMessage;
193                     } else if (ERROR_INFO.equals(rpcErrorChild.getLocalName())) {
194                         errorInfo = parseErrorInfo(rpcErrorChild);
195                     }
196                 }
197             }
198         }
199
200         if (rpcErrorCount > 1) {
201             if (errorInfo == null) {
202                 errorInfo = new HashMap<>();
203             }
204             errorInfo.put("Multiple Errors Found", allErrorMessages);
205         }
206
207         return new DocumentedException(errorMessage, errorType, errorTag, errorSeverity, errorInfo);
208     }
209
210     private static Map<String, String> parseErrorInfo(final Node node) {
211         Map<String, String> infoMap = new HashMap<>();
212         NodeList children = node.getChildNodes();
213         for (int i = 0; i < children.getLength(); i++) {
214             Node child = children.item(i);
215             if (child.getNodeType() == Node.ELEMENT_NODE) {
216                 infoMap.put(child.getNodeName(), child.getTextContent());
217             }
218         }
219
220         return infoMap;
221     }
222
223     public ErrorType getErrorType() {
224         return this.errorType;
225     }
226
227     public ErrorTag getErrorTag() {
228         return this.errorTag;
229     }
230
231     public ErrorSeverity getErrorSeverity() {
232         return this.errorSeverity;
233     }
234
235     public Map<String, String> getErrorInfo() {
236         return this.errorInfo;
237     }
238
239     public Document toXMLDocument() {
240         Document doc = null;
241         try {
242             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
243
244             Node rpcReply = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
245             doc.appendChild(rpcReply);
246
247             Node rpcError = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR);
248             rpcReply.appendChild(rpcError);
249
250             rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().elementBody()));
251             rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().getTagValue()));
252             rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().elementBody()));
253             rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
254
255             Map<String, String> errorInfoMap = getErrorInfo();
256             if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
257                 /*
258                  * <error-info> <bad-attribute>message-id</bad-attribute>
259                  * <bad-element>rpc</bad-element> </error-info>
260                  */
261
262                 Node errorInfoNode = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO);
263                 errorInfoNode.setPrefix(rpcReply.getPrefix());
264                 rpcError.appendChild(errorInfoNode);
265
266                 for (Entry<String, String> entry : errorInfoMap.entrySet()) {
267                     errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
268                 }
269             }
270         } catch (final ParserConfigurationException e) {
271             // this shouldn't happen
272             LOG.error("Error outputting to XML document", e);
273         }
274
275         return doc;
276     }
277
278     private Node createTextNode(final Document doc, final String tag, final String textContent) {
279         Node node = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag);
280         node.setTextContent(textContent);
281         return node;
282     }
283
284     @Override
285     public String toString() {
286         return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
287                 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
288                 + this.errorInfo + '}';
289     }
290 }