Update RESTCONF error mapping
[netconf.git] / restconf / restconf-common / src / main / java / org / opendaylight / restconf / common / errors / RestconfError.java
1 /*
2  * Copyright (c) 2014 Brocade Communications 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.restconf.common.errors;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.io.Serializable;
13 import java.util.Locale;
14 import org.eclipse.jdt.annotation.NonNull;
15 import org.opendaylight.yangtools.yang.common.RpcError;
16 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19
20 /**
21  * Encapsulates a restconf error as defined in the ietf restconf draft.
22  *
23  * <br>
24  * <br>
25  * <b>Note:</b> Enumerations defined within are provided by the ietf restconf draft.
26  *
27  * @author Devin Avery
28  *     See also <a href="https://tools.ietf.org/html/draft-bierman-netconf-restconf-02">RESTCONF</a>.
29  */
30 public class RestconfError implements Serializable {
31     private static final Logger LOG = LoggerFactory.getLogger(RestconfError.class);
32     private static final long serialVersionUID = 1L;
33
34     // FIXME: remove this enum in favor of RpcError.ErrorType (or its equivalent)
35     public enum ErrorType {
36         /**
37          * Errors relating to the transport layer.
38          */
39         TRANSPORT,
40         /**
41          * Errors relating to the RPC or notification layer.
42          */
43         RPC,
44         /**
45          * Errors relating to the protocol operation layer.
46          */
47         PROTOCOL,
48         /**
49          * Errors relating to the server application layer.
50          */
51         APPLICATION;
52
53         public String getErrorTypeTag() {
54             return name().toLowerCase(Locale.ROOT);
55         }
56
57         public static ErrorType valueOfCaseInsensitive(final String value) {
58             try {
59                 return ErrorType.valueOf(ErrorType.class, value.toUpperCase(Locale.ROOT));
60             } catch (IllegalArgumentException e) {
61                 return APPLICATION;
62             }
63         }
64
65         public static @NonNull ErrorType valueOf(final RpcError.ErrorType errorType) {
66             switch (errorType) {
67                 case PROTOCOL:
68                     return PROTOCOL;
69                 case RPC:
70                     return RPC;
71                 case TRANSPORT:
72                     return TRANSPORT;
73                 case APPLICATION:
74                 default:
75                     return APPLICATION;
76             }
77         }
78     }
79
80     public enum ErrorTag {
81         IN_USE("in-use", 409 /* Conflict */),
82         INVALID_VALUE("invalid-value", 400 /* Bad Request */),
83         TOO_BIG("too-big", 413 /* Request Entity Too Large */),
84         MISSING_ATTRIBUTE("missing-attribute", 400 /* Bad Request */),
85         BAD_ATTRIBUTE("bad-attribute", 400 /* Bad Request */),
86         UNKNOWN_ATTRIBUTE("unknown-attribute", 400 /* Bad Request */),
87         MISSING_ELEMENT("missing-element", 400 /* Bad Request */),
88         BAD_ELEMENT("bad-element", 400 /* Bad Request */),
89         UNKNOWN_ELEMENT("unknown-element", 400 /* Bad Request */),
90         UNKNOWN_NAMESPACE("unknown-namespace", 400 /* Bad Request */),
91         ACCESS_DENIED("access-denied", 403 /* Forbidden */),
92         LOCK_DENIED("lock-denied", 409 /* Conflict */),
93         RESOURCE_DENIED("resource-denied", 409 /* Conflict */),
94         ROLLBACK_FAILED("rollback-failed", 500 /* INTERNAL_SERVER_ERROR */),
95         DATA_EXISTS("data-exists", 409 /* Conflict */),
96         DATA_MISSING("data-missing", dataMissingHttpStatus()),
97         OPERATION_NOT_SUPPORTED("operation-not-supported", 501 /* Not Implemented */),
98         OPERATION_FAILED("operation-failed", 500 /* INTERNAL_SERVER_ERROR */),
99         PARTIAL_OPERATION("partial-operation", 500 /* INTERNAL_SERVER_ERROR */),
100         MALFORMED_MESSAGE("malformed-message", 400 /* Bad Request */),
101         RESOURCE_DENIED_TRANSPORT("resource-denied-transport", 503 /* Service Unavailable */);
102
103         private final String tagValue;
104         private final int statusCode;
105
106         ErrorTag(final String tagValue, final int statusCode) {
107             this.tagValue = tagValue;
108             this.statusCode = statusCode;
109         }
110
111         public String getTagValue() {
112             return this.tagValue.toLowerCase(Locale.ROOT);
113         }
114
115         public static ErrorTag valueOfCaseInsensitive(final String value) {
116             try {
117                 return ErrorTag.valueOf(ErrorTag.class, value.toUpperCase(Locale.ROOT).replaceAll("-", "_"));
118             } catch (IllegalArgumentException e) {
119                 return OPERATION_FAILED;
120             }
121         }
122
123         public int getStatusCode() {
124             return statusCode;
125         }
126
127         private static int dataMissingHttpStatus() {
128             // Control over the HTTP status reported on "data-missing" conditions. This defaults to disabled,
129             // HTTP status 409 as specified by RFC8040 (and all previous drafts). See the discussion in:
130             // https://www.rfc-editor.org/errata/eid5565
131             // https://mailarchive.ietf.org/arch/msg/netconf/hkVDdHK4xA74NgvXzWP0zObMiyY/
132             final String propName = "org.opendaylight.restconf.eid5565";
133             final String propValue = System.getProperty(propName, "disabled");
134             switch (propValue) {
135                 case "enabled":
136                     // RFC7231 interpretation: 404 Not Found
137                     LOG.info("RESTCONF data-missing condition is reported as HTTP status 404 (Errata 5565)");
138                     return 404;
139                 case "disabled":
140                     break;
141                 default:
142                     LOG.warn("Unhandled {} value \"{}\", assuming disabled", propName, propValue);
143             }
144
145             // RFC8040 specification: 409 Conflict
146             return 409;
147         }
148     }
149
150     private final ErrorType errorType;
151     private final ErrorTag errorTag;
152     private final String errorInfo;
153     private final String errorAppTag;
154     private final String errorMessage;
155     private final YangInstanceIdentifier errorPath;
156
157     /**
158      * Constructs a RestConfError.
159      *
160      * @param errorType
161      *            The enumerated type indicating the layer where the error occurred.
162      * @param errorTag
163      *            The enumerated tag representing a more specific error cause.
164      * @param errorMessage
165      *            A string which provides a plain text string describing the error.
166      */
167     public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage) {
168         this(errorType, errorTag, errorMessage, null, null, null);
169     }
170
171     /**
172      * Constructs a RestConfError object.
173      *
174      * @param errorType
175      *            The enumerated type indicating the layer where the error occurred.
176      * @param errorTag
177      *            The enumerated tag representing a more specific error cause.
178      * @param errorMessage
179      *            A string which provides a plain text string describing the error.
180      * @param errorAppTag
181      *            A string which represents an application-specific error tag that further specifies the error cause.
182      */
183     public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
184                          final String errorAppTag) {
185         this(errorType, errorTag, errorMessage, errorAppTag, null, null);
186     }
187
188     /**
189      * Constructs a RestConfError object.
190      *
191      * @param errorType
192      *            The enumerated type indicating the layer where the error occurred.
193      * @param errorTag
194      *            The enumerated tag representing a more specific error cause.
195      * @param errorMessage
196      *            A string which provides a plain text string describing the error.
197      * @param errorPath
198      *            An instance identifier which contains error path
199      */
200     public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
201                          final YangInstanceIdentifier errorPath) {
202         this(errorType, errorTag, errorMessage, null, null, errorPath);
203     }
204
205     /**
206      * Constructs a RestConfError object.
207      *
208      * @param errorType
209      *            The enumerated type indicating the layer where the error occurred.
210      * @param errorTag
211      *            The enumerated tag representing a more specific error cause.
212      * @param errorMessage
213      *            A string which provides a plain text string describing the error.
214      * @param errorAppTag
215      *            A string which represents an application-specific error tag that further specifies the error cause.
216      * @param errorInfo
217      *            A string, <b>formatted as XML</b>, which contains additional error information.
218      */
219     public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
220                          final String errorAppTag, final String errorInfo) {
221         this(errorType, errorTag, errorMessage, errorAppTag, errorInfo, null);
222     }
223
224     /**
225      * Constructs a RestConfError object.
226      *
227      * @param errorType
228      *            The enumerated type indicating the layer where the error occurred.
229      * @param errorTag
230      *            The enumerated tag representing a more specific error cause.
231      * @param errorMessage
232      *            A string which provides a plain text string describing the error.
233      * @param errorAppTag
234      *            A string which represents an application-specific error tag that further specifies the error cause.
235      * @param errorInfo
236      *            A string, <b>formatted as XML</b>, which contains additional error information.
237      * @param errorPath
238      *            An instance identifier which contains error path
239      */
240     public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
241                          final String errorAppTag, final String errorInfo, final YangInstanceIdentifier errorPath) {
242         this.errorType = requireNonNull(errorType, "Error type is required for RestConfError");
243         this.errorTag = requireNonNull(errorTag, "Error tag is required for RestConfError");
244         this.errorMessage = errorMessage;
245         this.errorAppTag = errorAppTag;
246         this.errorInfo = errorInfo;
247         this.errorPath = errorPath;
248     }
249
250     /**
251      * Constructs a RestConfError object from an RpcError.
252      */
253     public RestconfError(final RpcError rpcError) {
254
255         this.errorType = rpcError.getErrorType() == null ? ErrorType.APPLICATION : ErrorType
256                 .valueOfCaseInsensitive(rpcError.getErrorType().name());
257
258         this.errorTag = rpcError.getTag() == null ? ErrorTag.OPERATION_FAILED : ErrorTag
259                 .valueOfCaseInsensitive(rpcError.getTag());
260
261         this.errorMessage = rpcError.getMessage();
262         this.errorAppTag = rpcError.getApplicationTag();
263
264         String localErrorInfo = null;
265         if (rpcError.getInfo() == null) {
266             if (rpcError.getCause() != null) {
267                 localErrorInfo = rpcError.getCause().getMessage();
268             } else if (rpcError.getSeverity() != null) {
269                 localErrorInfo = "<severity>" + rpcError.getSeverity().toString().toLowerCase(Locale.ROOT)
270                         + "</severity>";
271             }
272         } else {
273             localErrorInfo = rpcError.getInfo();
274         }
275
276         this.errorInfo = localErrorInfo;
277         this.errorPath = null;
278     }
279
280     public ErrorType getErrorType() {
281         return errorType;
282     }
283
284     public ErrorTag getErrorTag() {
285         return errorTag;
286     }
287
288     public String getErrorInfo() {
289         return errorInfo;
290     }
291
292     public String getErrorAppTag() {
293         return errorAppTag;
294     }
295
296     public String getErrorMessage() {
297         return errorMessage;
298     }
299
300     public YangInstanceIdentifier getErrorPath() {
301         return errorPath;
302     }
303
304     @Override
305     public String toString() {
306         return "RestconfError ["
307                 + "error-type: " + errorType.getErrorTypeTag() + ", error-tag: " + errorTag.getTagValue()
308                 + (errorAppTag != null ? ", error-app-tag: " + errorAppTag : "")
309                 + (errorMessage != null ? ", error-message: " + errorMessage : "")
310                 + (errorInfo != null ? ", error-info: " + errorInfo : "")
311                 + (errorPath != null ? ", error-path: " + errorPath.toString() : "")
312                 + "]";
313     }
314 }