Refactor DocumentedException class
[controller.git] / opendaylight / config / config-util / src / main / java / org / opendaylight / controller / config / util / xml / 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
9 package org.opendaylight.controller.config.util.xml;
10
11 import static org.opendaylight.controller.config.util.xml.XmlMappingConstants.RPC_REPLY_KEY;
12 import static org.opendaylight.controller.config.util.xml.XmlMappingConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.Map.Entry;
17 import javax.xml.parsers.DocumentBuilderFactory;
18 import javax.xml.parsers.ParserConfigurationException;
19 import org.opendaylight.controller.config.api.ConflictingVersionException;
20 import org.opendaylight.controller.config.api.ValidationException;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23 import org.w3c.dom.Document;
24 import org.w3c.dom.Node;
25 import org.w3c.dom.NodeList;
26
27 /**
28  * Checked exception to communicate an error that needs to be sent to the
29  * netconf client.
30  */
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
44     private static final Logger LOG = LoggerFactory.getLogger(DocumentedException.class);
45
46     private static final DocumentBuilderFactory BUILDER_FACTORY;
47
48     static {
49         BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
50         try {
51             BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
52             BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
53             BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
54             BUILDER_FACTORY.setXIncludeAware(false);
55             BUILDER_FACTORY.setExpandEntityReferences(false);
56         } catch (ParserConfigurationException e) {
57             throw new ExceptionInInitializerError(e);
58         }
59         BUILDER_FACTORY.setNamespaceAware(true);
60         BUILDER_FACTORY.setCoalescing(true);
61         BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
62         BUILDER_FACTORY.setIgnoringComments(true);
63     }
64
65     public enum ErrorType {
66         TRANSPORT, RPC, PROTOCOL, APPLICATION;
67
68         public String getTypeValue() {
69             return name();
70         }
71
72         /**
73          * @deprecated Use {@link #getTypeValue()} instead.
74          */
75         @Deprecated
76         public String getTagValue() {
77             return name();
78         }
79
80         public static ErrorType from( String text ) {
81             try {
82                 return valueOf( text.toUpperCase() );
83             }
84             catch( Exception e ) {
85                 return APPLICATION;
86             }
87         }
88     }
89
90     public enum ErrorTag {
91         ACCESS_DENIED("access-denied"),
92         BAD_ATTRIBUTE("bad-attribute"),
93         BAD_ELEMENT("bad-element"),
94         DATA_EXISTS("data-exists"),
95         DATA_MISSING("data-missing"),
96         IN_USE("in-use"),
97         INVALID_VALUE("invalid-value"),
98         LOCK_DENIED("lock-denied"),
99         MALFORMED_MESSAGE("malformed-message"),
100         MISSING_ATTRIBUTE("missing-attribute"),
101         MISSING_ELEMENT("missing-element"),
102         OPERATION_FAILED("operation-failed"),
103         OPERATION_NOT_SUPPORTED("operation-not-supported"),
104         RESOURCE_DENIED("resource-denied"),
105         ROLLBCK_FAILED("rollback-failed"),
106         TOO_BIG("too-big"),
107         UNKNOWN_ATTRIBUTE("unknown-attribute"),
108         UNKNOWN_ELEMENT("unknown-element"),
109         UNKNOWN_NAMESPACE("unknown-namespace");
110
111         private final String tagValue;
112
113         ErrorTag(final String tagValue) {
114             this.tagValue = tagValue;
115         }
116
117         public String getTagValue() {
118             return this.tagValue;
119         }
120
121         public static ErrorTag from( String text ) {
122             for( ErrorTag e: values() )
123             {
124                 if( e.getTagValue().equals( text ) ) {
125                     return e;
126                 }
127             }
128
129             return OPERATION_FAILED;
130         }
131     }
132
133     public enum ErrorSeverity {
134         ERROR, WARNING;
135
136         public String getSeverityValue() {
137             return name();
138         }
139
140         /**
141          * @deprecated Use {@link #getSeverityValue()} instead.
142          */
143         @Deprecated
144         public String getTagValue() {
145             return name();
146         }
147
148         public static ErrorSeverity from( String text ) {
149             try {
150                 return valueOf( text.toUpperCase() );
151             }
152             catch( Exception e ) {
153                 return ERROR;
154             }
155         }
156     }
157
158     private final ErrorType errorType;
159     private final ErrorTag errorTag;
160     private final ErrorSeverity errorSeverity;
161     private final Map<String, String> errorInfo;
162
163     public DocumentedException(String message) {
164         this(message,
165                 DocumentedException.ErrorType.APPLICATION,
166                 DocumentedException.ErrorTag.INVALID_VALUE,
167                 DocumentedException.ErrorSeverity.ERROR
168         );
169     }
170
171     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
172                                final ErrorSeverity errorSeverity) {
173         this(message, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
174     }
175
176     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
177                                final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
178         super(message);
179         this.errorType = errorType;
180         this.errorTag = errorTag;
181         this.errorSeverity = errorSeverity;
182         this.errorInfo = errorInfo;
183     }
184
185     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
186                                final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
187         this(message, cause, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
188     }
189
190     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
191                                final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
192         super(message, cause);
193         this.errorType = errorType;
194         this.errorTag = errorTag;
195         this.errorSeverity = errorSeverity;
196         this.errorInfo = errorInfo;
197     }
198
199     public static <E extends Exception> DocumentedException wrap(E exception) throws DocumentedException {
200         final Map<String, String> errorInfo = new HashMap<>();
201         errorInfo.put(ErrorTag.OPERATION_FAILED.name(), "Exception thrown");
202         throw new DocumentedException(exception.getMessage(), exception, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
203                 ErrorSeverity.ERROR, errorInfo);
204     }
205     public static DocumentedException wrap(ValidationException e) throws DocumentedException {
206         final Map<String, String> errorInfo = new HashMap<>();
207         errorInfo.put(ErrorTag.OPERATION_FAILED.name(), "Validation failed");
208         throw new DocumentedException(e.getMessage(), e, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
209                 ErrorSeverity.ERROR, errorInfo);
210     }
211
212     public static DocumentedException wrap(ConflictingVersionException e) throws DocumentedException {
213         final Map<String, String> errorInfo = new HashMap<>();
214         errorInfo.put(ErrorTag.OPERATION_FAILED.name(), "Optimistic lock failed");
215         throw new DocumentedException(e.getMessage(), e, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
216                 ErrorSeverity.ERROR, errorInfo);
217     }
218
219     public static DocumentedException fromXMLDocument( Document fromDoc ) {
220
221         ErrorType errorType = ErrorType.APPLICATION;
222         ErrorTag errorTag = ErrorTag.OPERATION_FAILED;
223         ErrorSeverity errorSeverity = ErrorSeverity.ERROR;
224         Map<String, String> errorInfo = null;
225         String errorMessage = "";
226
227         Node rpcReply = fromDoc.getDocumentElement();
228
229         // FIXME: BUG? - we only handle one rpc-error.
230
231         NodeList replyChildren = rpcReply.getChildNodes();
232         for( int i = 0; i < replyChildren.getLength(); i++ ) {
233             Node replyChild = replyChildren.item( i );
234             if( RPC_ERROR.equals( replyChild.getNodeName() ) )
235             {
236                 NodeList rpcErrorChildren = replyChild.getChildNodes();
237                 for( int j = 0; j < rpcErrorChildren.getLength(); j++ )
238                 {
239                     Node rpcErrorChild = rpcErrorChildren.item( j );
240                     if( ERROR_TYPE.equals( rpcErrorChild.getNodeName() ) ) {
241                         errorType = ErrorType.from(rpcErrorChild.getTextContent());
242                     }
243                     else if( ERROR_TAG.equals( rpcErrorChild.getNodeName() ) ) {
244                         errorTag = ErrorTag.from(rpcErrorChild.getTextContent());
245                     }
246                     else if( ERROR_SEVERITY.equals( rpcErrorChild.getNodeName() ) ) {
247                         errorSeverity = ErrorSeverity.from(rpcErrorChild.getTextContent());
248                     }
249                     else if( ERROR_MESSAGE.equals( rpcErrorChild.getNodeName() ) ) {
250                         errorMessage = rpcErrorChild.getTextContent();
251                     }
252                     else if( ERROR_INFO.equals( rpcErrorChild.getNodeName() ) ) {
253                         errorInfo = parseErrorInfo( rpcErrorChild );
254                     }
255                 }
256
257                 break;
258             }
259         }
260
261         return new DocumentedException( errorMessage, errorType, errorTag, errorSeverity, errorInfo );
262     }
263
264     private static Map<String, String> parseErrorInfo( Node node ) {
265         Map<String, String> infoMap = new HashMap<>();
266         NodeList children = node.getChildNodes();
267         for( int i = 0; i < children.getLength(); i++ ) {
268             Node child = children.item( i );
269             if( child.getNodeType() == Node.ELEMENT_NODE ) {
270                 infoMap.put( child.getNodeName(), child.getTextContent() );
271             }
272         }
273
274         return infoMap;
275     }
276
277     public ErrorType getErrorType() {
278         return this.errorType;
279     }
280
281     public ErrorTag getErrorTag() {
282         return this.errorTag;
283     }
284
285     public ErrorSeverity getErrorSeverity() {
286         return this.errorSeverity;
287     }
288
289     public Map<String, String> getErrorInfo() {
290         return this.errorInfo;
291     }
292
293     public Document toXMLDocument() {
294         Document doc = null;
295         try {
296             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
297
298             Node rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
299             doc.appendChild( rpcReply );
300
301             Node rpcError = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR );
302             rpcReply.appendChild( rpcError );
303
304             rpcError.appendChild( createTextNode( doc, ERROR_TYPE, getErrorType().getTypeValue() ) );
305             rpcError.appendChild( createTextNode( doc, ERROR_TAG, getErrorTag().getTagValue() ) );
306             rpcError.appendChild( createTextNode( doc, ERROR_SEVERITY, getErrorSeverity().getSeverityValue() ) );
307             rpcError.appendChild( createTextNode( doc, ERROR_MESSAGE, getLocalizedMessage() ) );
308
309             Map<String, String> errorInfoMap = getErrorInfo();
310             if( errorInfoMap != null && !errorInfoMap.isEmpty() ) {
311                 /*
312                  * <error-info>
313                  *   <bad-attribute>message-id</bad-attribute>
314                  *   <bad-element>rpc</bad-element>
315                  * </error-info>
316                  */
317
318                 Node errorInfoNode = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO );
319                 errorInfoNode.setPrefix( rpcReply.getPrefix() );
320                 rpcError.appendChild( errorInfoNode );
321
322                 for ( Entry<String, String> entry : errorInfoMap.entrySet() ) {
323                     errorInfoNode.appendChild( createTextNode( doc, entry.getKey(), entry.getValue() ) );
324                 }
325             }
326         }
327         catch( ParserConfigurationException e ) {
328             // this shouldn't happen
329             LOG.error("Error outputting to XML document", e);
330         }
331
332         return doc;
333     }
334
335     private Node createTextNode( Document doc, String tag, String textContent ) {
336         Node node = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag );
337         node.setTextContent( textContent );
338         return node;
339     }
340
341     @Override
342     public String toString() {
343         return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
344                 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
345                 + this.errorInfo + '}';
346     }
347 }