Use Object.requireNonNull
[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 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;
13
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.Map;
17 import java.util.Map.Entry;
18 import javax.xml.parsers.DocumentBuilderFactory;
19 import javax.xml.parsers.ParserConfigurationException;
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 ErrorType {
65         TRANSPORT("transport"), RPC("rpc"), PROTOCOL("protocol"), APPLICATION("application");
66
67         private final String typeValue;
68
69         ErrorType(final String typeValue) {
70             this.typeValue = requireNonNull(typeValue);
71         }
72
73         public String getTypeValue() {
74             return this.typeValue;
75         }
76
77         public static ErrorType from(final String text) {
78             for (ErrorType e : values()) {
79                 if (e.getTypeValue().equalsIgnoreCase(text)) {
80                     return e;
81                 }
82             }
83
84             return APPLICATION;
85         }
86     }
87
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"),
94         IN_USE("in-use"),
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"),
104         TOO_BIG("too-big"),
105         UNKNOWN_ATTRIBUTE("unknown-attribute"),
106         UNKNOWN_ELEMENT("unknown-element"),
107         UNKNOWN_NAMESPACE("unknown-namespace");
108
109         private final String tagValue;
110
111         ErrorTag(final String tagValue) {
112             this.tagValue = tagValue;
113         }
114
115         public String getTagValue() {
116             return this.tagValue;
117         }
118
119         public static ErrorTag from(final String text) {
120             for (ErrorTag e : values()) {
121                 if (e.getTagValue().equals(text)) {
122                     return e;
123                 }
124             }
125
126             return OPERATION_FAILED;
127         }
128     }
129
130     public enum ErrorSeverity {
131         ERROR("error"), WARNING("warning");
132
133         private final String severityValue;
134
135         ErrorSeverity(final String severityValue) {
136             this.severityValue = requireNonNull(severityValue);
137         }
138
139         public String getSeverityValue() {
140             return this.severityValue;
141         }
142
143         public static ErrorSeverity from(final String text) {
144             for (ErrorSeverity e : values()) {
145                 if (e.getSeverityValue().equalsIgnoreCase(text)) {
146                     return e;
147                 }
148             }
149
150             return ERROR;
151         }
152     }
153
154     private final ErrorType errorType;
155     private final ErrorTag errorTag;
156     private final ErrorSeverity errorSeverity;
157     private final Map<String, String> errorInfo;
158
159     public DocumentedException(final String message) {
160         this(message, DocumentedException.ErrorType.APPLICATION, DocumentedException.ErrorTag.INVALID_VALUE,
161                 DocumentedException.ErrorSeverity.ERROR);
162     }
163
164     public DocumentedException(final String message, final Exception cause) {
165         this(message, cause, DocumentedException.ErrorType.APPLICATION, DocumentedException.ErrorTag.INVALID_VALUE,
166                 DocumentedException.ErrorSeverity.ERROR);
167     }
168
169     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
170             final ErrorSeverity errorSeverity) {
171         this(message, errorType, errorTag, errorSeverity, Collections.emptyMap());
172     }
173
174     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
175             final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
176         super(message);
177         this.errorType = errorType;
178         this.errorTag = errorTag;
179         this.errorSeverity = errorSeverity;
180         this.errorInfo = errorInfo;
181     }
182
183     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
184             final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
185         this(message, cause, errorType, errorTag, errorSeverity, Collections.emptyMap());
186     }
187
188     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
189             final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
190         super(message, cause);
191         this.errorType = errorType;
192         this.errorTag = errorTag;
193         this.errorSeverity = errorSeverity;
194         this.errorInfo = errorInfo;
195     }
196
197     public static <E extends Exception> DocumentedException wrap(final E exception) throws DocumentedException {
198         final Map<String, String> errorInfo = new HashMap<>();
199         errorInfo.put(ErrorTag.OPERATION_FAILED.name(), "Exception thrown");
200         throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION,
201                 ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR, errorInfo);
202     }
203
204     public static DocumentedException fromXMLDocument(final Document fromDoc) {
205
206         ErrorType errorType = ErrorType.APPLICATION;
207         ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
208         ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
209         Map<String, String> errorInfo = null;
210         String errorMessage = "";
211
212         Node rpcReply = fromDoc.getDocumentElement();
213
214         // FIXME: BUG? - we only handle one rpc-error.
215
216         NodeList replyChildren = rpcReply.getChildNodes();
217         for (int i = 0; i < replyChildren.getLength(); i++) {
218             Node replyChild = replyChildren.item(i);
219             if (RPC_ERROR.equals(replyChild.getNodeName())) {
220                 NodeList rpcErrorChildren = replyChild.getChildNodes();
221                 for (int j = 0; j < rpcErrorChildren.getLength(); j++) {
222                     Node rpcErrorChild = rpcErrorChildren.item(j);
223                     if (ERROR_TYPE.equals(rpcErrorChild.getNodeName())) {
224                         errorType = ErrorType.from(rpcErrorChild.getTextContent());
225                     } else if (ERROR_TAG.equals(rpcErrorChild.getNodeName())) {
226                         errorTag = ErrorTag.from(rpcErrorChild.getTextContent());
227                     } else if (ERROR_SEVERITY.equals(rpcErrorChild.getNodeName())) {
228                         errorSeverity = ErrorSeverity.from(rpcErrorChild.getTextContent());
229                     } else if (ERROR_MESSAGE.equals(rpcErrorChild.getNodeName())) {
230                         errorMessage = rpcErrorChild.getTextContent();
231                     } else if (ERROR_INFO.equals(rpcErrorChild.getNodeName())) {
232                         errorInfo = parseErrorInfo(rpcErrorChild);
233                     }
234                 }
235
236                 break;
237             }
238         }
239
240         return new DocumentedException(errorMessage, errorType, errorTag, errorSeverity, errorInfo);
241     }
242
243     private static Map<String, String> parseErrorInfo(final Node node) {
244         Map<String, String> infoMap = new HashMap<>();
245         NodeList children = node.getChildNodes();
246         for (int i = 0; i < children.getLength(); i++) {
247             Node child = children.item(i);
248             if (child.getNodeType() == Node.ELEMENT_NODE) {
249                 infoMap.put(child.getNodeName(), child.getTextContent());
250             }
251         }
252
253         return infoMap;
254     }
255
256     public ErrorType getErrorType() {
257         return this.errorType;
258     }
259
260     public ErrorTag getErrorTag() {
261         return this.errorTag;
262     }
263
264     public ErrorSeverity getErrorSeverity() {
265         return this.errorSeverity;
266     }
267
268     public Map<String, String> getErrorInfo() {
269         return this.errorInfo;
270     }
271
272     public Document toXMLDocument() {
273         Document doc = null;
274         try {
275             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
276
277             Node rpcReply = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
278             doc.appendChild(rpcReply);
279
280             Node rpcError = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR);
281             rpcReply.appendChild(rpcError);
282
283             rpcError.appendChild(createTextNode(doc, ERROR_TYPE, getErrorType().getTypeValue()));
284             rpcError.appendChild(createTextNode(doc, ERROR_TAG, getErrorTag().getTagValue()));
285             rpcError.appendChild(createTextNode(doc, ERROR_SEVERITY, getErrorSeverity().getSeverityValue()));
286             rpcError.appendChild(createTextNode(doc, ERROR_MESSAGE, getLocalizedMessage()));
287
288             Map<String, String> errorInfoMap = getErrorInfo();
289             if (errorInfoMap != null && !errorInfoMap.isEmpty()) {
290                 /*
291                  * <error-info> <bad-attribute>message-id</bad-attribute>
292                  * <bad-element>rpc</bad-element> </error-info>
293                  */
294
295                 Node errorInfoNode = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO);
296                 errorInfoNode.setPrefix(rpcReply.getPrefix());
297                 rpcError.appendChild(errorInfoNode);
298
299                 for (Entry<String, String> entry : errorInfoMap.entrySet()) {
300                     errorInfoNode.appendChild(createTextNode(doc, entry.getKey(), entry.getValue()));
301                 }
302             }
303         } catch (final ParserConfigurationException e) {
304             // this shouldn't happen
305             LOG.error("Error outputting to XML document", e);
306         }
307
308         return doc;
309     }
310
311     private Node createTextNode(final Document doc, final String tag, final String textContent) {
312         Node node = doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag);
313         node.setTextContent(textContent);
314         return node;
315     }
316
317     @Override
318     public String toString() {
319         return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
320                 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
321                 + this.errorInfo + '}';
322     }
323 }