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