Decouple config and netconf subsystems.
[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
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.opendaylight.controller.config.api.ConflictingVersionException;
21 import org.opendaylight.controller.config.api.ValidationException;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.w3c.dom.Document;
25 import org.w3c.dom.Node;
26 import org.w3c.dom.NodeList;
27
28 /**
29  * Checked exception to communicate an error that needs to be sent to the
30  * netconf client.
31  */
32 public class DocumentedException extends Exception {
33
34     public static final String RPC_ERROR = "rpc-error";
35     public static final String ERROR_TYPE = "error-type";
36     public static final String ERROR_TAG = "error-tag";
37     public static final String ERROR_SEVERITY = "error-severity";
38     public static final String ERROR_APP_TAG = "error-app-tag";
39     public static final String ERROR_PATH = "error-path";
40     public static final String ERROR_MESSAGE = "error-message";
41     public static final String ERROR_INFO = "error-info";
42
43     private static final long serialVersionUID = 1L;
44
45     private final static Logger LOG = LoggerFactory.getLogger( DocumentedException.class );
46
47     private static final DocumentBuilderFactory BUILDER_FACTORY;
48
49     static {
50         BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
51         try {
52             BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
53             BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
54             BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
55             BUILDER_FACTORY.setXIncludeAware(false);
56             BUILDER_FACTORY.setExpandEntityReferences(false);
57         } catch (ParserConfigurationException e) {
58             throw new ExceptionInInitializerError(e);
59         }
60         BUILDER_FACTORY.setNamespaceAware(true);
61         BUILDER_FACTORY.setCoalescing(true);
62         BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
63         BUILDER_FACTORY.setIgnoringComments(true);
64     }
65
66     public enum ErrorType {
67         transport, rpc, protocol, application;
68
69         public String getTagValue() {
70             return name();
71         }
72
73         public static ErrorType from( String text ) {
74             try {
75                 return valueOf( text );
76             }
77             catch( Exception e ) {
78                 return application;
79             }
80         }
81     }
82
83     public enum ErrorTag {
84         access_denied("access-denied"),
85         bad_attribute("bad-attribute"),
86         bad_element("bad-element"),
87         data_exists("data-exists"),
88         data_missing("data-missing"),
89         in_use("in-use"),
90         invalid_value("invalid-value"),
91         lock_denied("lock-denied"),
92         malformed_message("malformed-message"),
93         missing_attribute("missing-attribute"),
94         missing_element("missing-element"),
95         operation_failed("operation-failed"),
96         operation_not_supported("operation-not-supported"),
97         resource_denied("resource-denied"),
98         rollback_failed("rollback-failed"),
99         too_big("too-big"),
100         unknown_attribute("unknown-attribute"),
101         unknown_element("unknown-element"),
102         unknown_namespace("unknown-namespace");
103
104         private final String tagValue;
105
106         ErrorTag(final String tagValue) {
107             this.tagValue = tagValue;
108         }
109
110         public String getTagValue() {
111             return this.tagValue;
112         }
113
114         public static ErrorTag from( String text ) {
115             for( ErrorTag e: values() )
116             {
117                 if( e.getTagValue().equals( text ) ) {
118                     return e;
119                 }
120             }
121
122             return operation_failed;
123         }
124     }
125
126     public enum ErrorSeverity {
127         error, warning;
128
129         public String getTagValue() {
130             return name();
131         }
132
133         public static ErrorSeverity from( String text ) {
134             try {
135                 return valueOf( text );
136             }
137             catch( Exception e ) {
138                 return error;
139             }
140         }
141     }
142
143     private final ErrorType errorType;
144     private final ErrorTag errorTag;
145     private final ErrorSeverity errorSeverity;
146     private final Map<String, String> errorInfo;
147
148     public DocumentedException(String message) {
149         this(message,
150                 DocumentedException.ErrorType.application,
151                 DocumentedException.ErrorTag.invalid_value,
152                 DocumentedException.ErrorSeverity.error
153         );
154     }
155
156     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
157                                final ErrorSeverity errorSeverity) {
158         this(message, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
159     }
160
161     public DocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
162                                final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
163         super(message);
164         this.errorType = errorType;
165         this.errorTag = errorTag;
166         this.errorSeverity = errorSeverity;
167         this.errorInfo = errorInfo;
168     }
169
170     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
171                                final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
172         this(message, cause, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
173     }
174
175     public DocumentedException(final String message, final Exception cause, final ErrorType errorType,
176                                final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
177         super(message, cause);
178         this.errorType = errorType;
179         this.errorTag = errorTag;
180         this.errorSeverity = errorSeverity;
181         this.errorInfo = errorInfo;
182     }
183
184     public static <E extends Exception> DocumentedException wrap(E exception) throws DocumentedException {
185         final Map<String, String> errorInfo = new HashMap<>();
186         errorInfo.put(ErrorTag.operation_failed.name(), "Exception thrown");
187         throw new DocumentedException(exception.getMessage(), exception, ErrorType.application, ErrorTag.operation_failed,
188                 ErrorSeverity.error, errorInfo);
189     }
190     public static DocumentedException wrap(ValidationException e) throws DocumentedException {
191         final Map<String, String> errorInfo = new HashMap<>();
192         errorInfo.put(ErrorTag.operation_failed.name(), "Validation failed");
193         throw new DocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed,
194                 ErrorSeverity.error, errorInfo);
195     }
196
197     public static DocumentedException wrap(ConflictingVersionException e) throws DocumentedException {
198         final Map<String, String> errorInfo = new HashMap<>();
199         errorInfo.put(ErrorTag.operation_failed.name(), "Optimistic lock failed");
200         throw new DocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed,
201                 ErrorSeverity.error, errorInfo);
202     }
203
204     public static DocumentedException fromXMLDocument( 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             {
221                 NodeList rpcErrorChildren = replyChild.getChildNodes();
222                 for( int j = 0; j < rpcErrorChildren.getLength(); j++ )
223                 {
224                     Node rpcErrorChild = rpcErrorChildren.item( j );
225                     if( ERROR_TYPE.equals( rpcErrorChild.getNodeName() ) ) {
226                         errorType = ErrorType.from(rpcErrorChild.getTextContent());
227                     }
228                     else if( ERROR_TAG.equals( rpcErrorChild.getNodeName() ) ) {
229                         errorTag = ErrorTag.from(rpcErrorChild.getTextContent());
230                     }
231                     else if( ERROR_SEVERITY.equals( rpcErrorChild.getNodeName() ) ) {
232                         errorSeverity = ErrorSeverity.from(rpcErrorChild.getTextContent());
233                     }
234                     else if( ERROR_MESSAGE.equals( rpcErrorChild.getNodeName() ) ) {
235                         errorMessage = rpcErrorChild.getTextContent();
236                     }
237                     else if( ERROR_INFO.equals( rpcErrorChild.getNodeName() ) ) {
238                         errorInfo = parseErrorInfo( rpcErrorChild );
239                     }
240                 }
241
242                 break;
243             }
244         }
245
246         return new DocumentedException( errorMessage, errorType, errorTag, errorSeverity, errorInfo );
247     }
248
249     private static Map<String, String> parseErrorInfo( Node node ) {
250         Map<String, String> infoMap = new HashMap<>();
251         NodeList children = node.getChildNodes();
252         for( int i = 0; i < children.getLength(); i++ ) {
253             Node child = children.item( i );
254             if( child.getNodeType() == Node.ELEMENT_NODE ) {
255                 infoMap.put( child.getNodeName(), child.getTextContent() );
256             }
257         }
258
259         return infoMap;
260     }
261
262     public ErrorType getErrorType() {
263         return this.errorType;
264     }
265
266     public ErrorTag getErrorTag() {
267         return this.errorTag;
268     }
269
270     public ErrorSeverity getErrorSeverity() {
271         return this.errorSeverity;
272     }
273
274     public Map<String, String> getErrorInfo() {
275         return this.errorInfo;
276     }
277
278     public Document toXMLDocument() {
279         Document doc = null;
280         try {
281             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
282
283             Node rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY);
284             doc.appendChild( rpcReply );
285
286             Node rpcError = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR );
287             rpcReply.appendChild( rpcError );
288
289             rpcError.appendChild( createTextNode( doc, ERROR_TYPE, getErrorType().getTagValue() ) );
290             rpcError.appendChild( createTextNode( doc, ERROR_TAG, getErrorTag().getTagValue() ) );
291             rpcError.appendChild( createTextNode( doc, ERROR_SEVERITY, getErrorSeverity().getTagValue() ) );
292             rpcError.appendChild( createTextNode( doc, ERROR_MESSAGE, getLocalizedMessage() ) );
293
294             Map<String, String> errorInfoMap = getErrorInfo();
295             if( errorInfoMap != null && !errorInfoMap.isEmpty() ) {
296                 /*
297                  * <error-info>
298                  *   <bad-attribute>message-id</bad-attribute>
299                  *   <bad-element>rpc</bad-element>
300                  * </error-info>
301                  */
302
303                 Node errorInfoNode = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO );
304                 errorInfoNode.setPrefix( rpcReply.getPrefix() );
305                 rpcError.appendChild( errorInfoNode );
306
307                 for ( Entry<String, String> entry : errorInfoMap.entrySet() ) {
308                     errorInfoNode.appendChild( createTextNode( doc, entry.getKey(), entry.getValue() ) );
309                 }
310             }
311         }
312         catch( ParserConfigurationException e ) {
313             LOG.error( "Error outputting to XML document", e ); // this shouldn't happen
314         }
315
316         return doc;
317     }
318
319     private Node createTextNode( Document doc, String tag, String textContent ) {
320         Node node = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag );
321         node.setTextContent( textContent );
322         return node;
323     }
324
325     @Override
326     public String toString() {
327         return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
328                 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
329                 + this.errorInfo + '}';
330     }
331 }