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