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