cc5f15b2bb4892cd5738cda8599b7ac9c0c687f9
[netconf.git] / restconf / restconf-common / src / main / java / org / opendaylight / restconf / common / errors / RestconfDocumentedException.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.util.ArrayList;
13 import java.util.Collection;
14 import java.util.List;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.eclipse.jdt.annotation.Nullable;
17 import org.opendaylight.yangtools.yang.common.ErrorTag;
18 import org.opendaylight.yangtools.yang.common.ErrorType;
19 import org.opendaylight.yangtools.yang.common.RpcError;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
22 import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
23 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
24
25 /**
26  * Unchecked exception to communicate error information, as defined
27  * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.9">"errors" YANG Data Template</a>.
28  *
29  * @author Devin Avery
30  * @author Thomas Pantelis
31  */
32 public class RestconfDocumentedException extends RuntimeException {
33     @java.io.Serial
34     private static final long serialVersionUID = 3L;
35
36     private final List<RestconfError> errors;
37
38     // FIXME: this field should be non-null
39     private final transient @Nullable EffectiveModelContext modelContext;
40
41     /**
42      * Constructs an instance with an error message. The error type defaults to APPLICATION and the error tag defaults
43      * to OPERATION_FAILED.
44      *
45      * @param message
46      *            A string which provides a plain text string describing the error.
47      */
48     public RestconfDocumentedException(final String message) {
49         this(message, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
50     }
51
52     /**
53      * Constructs an instance with an error message, error type, error tag and exception cause.
54      *
55      * @param message
56      *            A string which provides a plain text string describing the error.
57      * @param errorType
58      *            The enumerated type indicating the layer where the error occurred.
59      * @param errorTag
60      *            The enumerated tag representing a more specific error cause.
61      * @param cause
62      *            The underlying exception cause.
63      */
64     public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
65                                        final Throwable cause) {
66         this(cause, new RestconfError(errorType, errorTag, message, null, cause.getMessage(), null));
67     }
68
69     /**
70      * Constructs an instance with an error message, error type, and error tag.
71      *
72      * @param message
73      *            A string which provides a plain text string describing the error.
74      * @param errorType
75      *            The enumerated type indicating the layer where the error occurred.
76      * @param errorTag
77      *            The enumerated tag representing a more specific error cause.
78      */
79     public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag) {
80         this(null, new RestconfError(errorType, errorTag, message));
81     }
82
83     /**
84      * Constructs an instance with an error message, error type, error tag and error path.
85      *
86      * @param message
87      *            A string which provides a plain text string describing the error.
88      * @param errorType
89      *            The enumerated type indicating the layer where the error occurred.
90      * @param errorTag
91      *            The enumerated tag representing a more specific error cause.
92      * @param errorPath
93      *            The instance identifier representing error path
94      */
95     public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
96                                        final YangInstanceIdentifier errorPath) {
97         this(null, new RestconfError(errorType, errorTag, message, errorPath));
98     }
99
100     /**
101      * Constructs an instance with an error message and exception cause.
102      * The underlying exception is included in the error-info.
103      *
104      * @param message
105      *            A string which provides a plain text string describing the error.
106      * @param cause
107      *            The underlying exception cause.
108      */
109     public RestconfDocumentedException(final String message, final Throwable cause) {
110         this(cause, new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message, null,
111             cause.getMessage(), null));
112     }
113
114     /**
115      * Constructs an instance with the given error.
116      */
117     public RestconfDocumentedException(final RestconfError error) {
118         this(null, error);
119     }
120
121     /**
122      * Constructs an instance with the given errors.
123      */
124     public RestconfDocumentedException(final String message, final Throwable cause, final List<RestconfError> errors) {
125         // FIXME: We override getMessage so supplied message is lost for any public access
126         // this was lost also in original code.
127         super(cause);
128         if (errors.isEmpty()) {
129             this.errors = List.of(new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message));
130         } else {
131             this.errors = List.copyOf(errors);
132         }
133
134         modelContext = null;
135     }
136
137     /**
138      * Constructs an instance with the given RpcErrors.
139      */
140     public RestconfDocumentedException(final String message, final Throwable cause,
141                                        final Collection<? extends RpcError> rpcErrors) {
142         this(message, cause, convertToRestconfErrors(rpcErrors));
143     }
144
145     public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
146         super(cause);
147         errors = List.of(error);
148         modelContext = null;
149     }
150
151     public RestconfDocumentedException(final Throwable cause, final RestconfError error,
152             final EffectiveModelContext modelContext) {
153         super(cause);
154         errors = List.of(error);
155         this.modelContext = requireNonNull(modelContext);
156     }
157
158     public RestconfDocumentedException(final Throwable cause, final List<RestconfError> errors) {
159         super(cause);
160         if (errors.isEmpty()) {
161             throw new IllegalArgumentException("At least one error is required");
162         }
163         this.errors = List.copyOf(errors);
164         modelContext = null;
165     }
166
167     /**
168      * Throw an instance of this exception if an expression evaluates to true. If the expression evaluates to false,
169      * this method does nothing.
170      *
171      * @param expression Expression to be evaluated
172      * @param errorType The enumerated type indicating the layer where the error occurred.
173      * @param errorTag The enumerated tag representing a more specific error cause.
174      * @param format Format string, according to {@link String#format(String, Object...)}.
175      * @param args Format string arguments, according to {@link String#format(String, Object...)}
176      * @throws RestconfDocumentedException if the expression evaluates to true.
177      */
178     public static void throwIf(final boolean expression, final ErrorType errorType, final ErrorTag errorTag,
179             final @NonNull String format, final Object... args) {
180         if (expression) {
181             throw new RestconfDocumentedException(String.format(format, args), errorType, errorTag);
182         }
183     }
184
185     /**
186      * Throw an instance of this exception if an expression evaluates to true. If the expression evaluates to false,
187      * this method does nothing.
188      *
189      * @param expression Expression to be evaluated
190      * @param message error message
191      * @param errorType The enumerated type indicating the layer where the error occurred.
192      * @param errorTag The enumerated tag representing a more specific error cause.
193      * @throws RestconfDocumentedException if the expression evaluates to true.
194      */
195     public static void throwIf(final boolean expression, final @NonNull String message,
196             final ErrorType errorType, final ErrorTag errorTag) {
197         if (expression) {
198             throw new RestconfDocumentedException(message, errorType, errorTag);
199         }
200     }
201
202     /**
203      * Throw an instance of this exception if an object is null. If the object is non-null, it will
204      * be returned as the result of this method.
205      *
206      * @param obj Object reference to be checked
207      * @param errorType The enumerated type indicating the layer where the error occurred.
208      * @param errorTag The enumerated tag representing a more specific error cause.
209      * @param format Format string, according to {@link String#format(String, Object...)}.
210      * @param args Format string arguments, according to {@link String#format(String, Object...)}
211      * @throws RestconfDocumentedException if the expression evaluates to true.
212      */
213     public static <T> @NonNull T throwIfNull(final @Nullable T obj, final ErrorType errorType, final ErrorTag errorTag,
214             final @NonNull String format, final Object... args) {
215         if (obj == null) {
216             throw new RestconfDocumentedException(String.format(format, args), errorType, errorTag);
217         }
218         return obj;
219     }
220
221     /**
222      * Throw an instance of this exception if the specified exception has a {@link YangNetconfError} attachment.
223      *
224      * @param cause Proposed cause of a RestconfDocumented exception
225      */
226     public static void throwIfYangError(final Throwable cause) {
227         if (cause instanceof YangNetconfErrorAware infoAware) {
228             throw new RestconfDocumentedException(cause, infoAware.getNetconfErrors().stream()
229                 .map(error -> new RestconfError(error.type(), error.tag(), error.message(), error.appTag(),
230                     // FIXME: pass down error info
231                     null, error.path()))
232                 .toList());
233         }
234     }
235
236     private static List<RestconfError> convertToRestconfErrors(final Collection<? extends RpcError> rpcErrors) {
237         if (rpcErrors == null || rpcErrors.isEmpty()) {
238             return List.of();
239         }
240
241         final var errorList = new ArrayList<RestconfError>();
242         for (var rpcError : rpcErrors) {
243             errorList.add(new RestconfError(rpcError));
244         }
245         return errorList;
246     }
247
248     @Override
249     public String getMessage() {
250         return "errors: " + errors;
251     }
252
253     /**
254      * Reference to {@link EffectiveModelContext} in which this exception was generated. This method will return
255      * {@code null} if this exception was serialized or if the context is not available.
256      *
257      * @return Reference model context
258      */
259     public @Nullable EffectiveModelContext modelContext() {
260         return modelContext;
261     }
262
263     public List<RestconfError> getErrors() {
264         return errors;
265     }
266 }