View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.exception;
18  
19  import java.util.List;
20  import java.util.Set;
21  
22  import org.apache.commons.lang3.tuple.Pair;
23  
24  /**
25   * An exception that provides an easy and safe way to add contextual information.
26   * <p>
27   * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
28   * Frequently what is needed is a select few pieces of local contextual data.
29   * Providing this data is tricky however, due to concerns over formatting and nulls.
30   * </p>
31   * <p>
32   * The contexted exception approach allows the exception to be created together with a
33   * list of context label-value pairs. This additional information is automatically included in
34   * the message and printed stack trace.
35   * </p>
36   * <p>
37   * An unchecked version of this exception is provided by ContextedRuntimeException.
38   * </p>
39   * <p>
40   * To use this class write code as follows:
41   * </p>
42   * <pre>
43   *   try {
44   *     ...
45   *   } catch (Exception e) {
46   *     throw new ContextedException("Error posting account transaction", e)
47   *          .addContextValue("Account Number", accountNumber)
48   *          .addContextValue("Amount Posted", amountPosted)
49   *          .addContextValue("Previous Balance", previousBalance);
50   *   }
51   * }
52   * </pre>
53   * <p>
54   * or improve diagnose data at a higher level:
55   * </p>
56   * <pre>
57   *   try {
58   *     ...
59   *   } catch (ContextedException e) {
60   *     throw e.setContextValue("Transaction Id", transactionId);
61   *   } catch (Exception e) {
62   *     if (e instanceof ExceptionContext) {
63   *       e.setContextValue("Transaction Id", transactionId);
64   *     }
65   *     throw e;
66   *   }
67   * }
68   * </pre>
69   * <p>
70   * The output in a printStacktrace() (which often is written to a log) would look something like the following:
71   * </p>
72   * <pre>
73   * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
74   *  Exception Context:
75   *  [1:Account Number=null]
76   *  [2:Amount Posted=100.00]
77   *  [3:Previous Balance=-2.17]
78   *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
79   *
80   *  ---------------------------------
81   *  at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
82   *  ..... (rest of trace)
83   * </pre>
84   *
85   * @see ContextedRuntimeException
86   * @since 3.0
87   */
88  public class ContextedException extends Exception implements ExceptionContext {
89  
90      /** The serialization version. */
91      private static final long serialVersionUID = 20110706L;
92  
93      /** The context where the data is stored. */
94      private final ExceptionContext exceptionContext;
95  
96      /**
97       * Instantiates ContextedException without message or cause.
98       * <p>
99       * The context information is stored using a default implementation.
100      */
101     public ContextedException() {
102         exceptionContext = new DefaultExceptionContext();
103     }
104 
105     /**
106      * Instantiates ContextedException with message, but without cause.
107      * <p>
108      * The context information is stored using a default implementation.
109      *
110      * @param message  The exception message, may be null
111      */
112     public ContextedException(final String message) {
113         super(message);
114         exceptionContext = new DefaultExceptionContext();
115     }
116 
117     /**
118      * Instantiates ContextedException with cause and message.
119      * <p>
120      * The context information is stored using a default implementation.
121      *
122      * @param message  The exception message, may be null
123      * @param cause  The underlying cause of the exception, may be null
124      */
125     public ContextedException(final String message, final Throwable cause) {
126         super(message, cause);
127         exceptionContext = new DefaultExceptionContext();
128     }
129 
130     /**
131      * Instantiates ContextedException with cause, message, and ExceptionContext.
132      *
133      * @param message  The exception message, may be null
134      * @param cause  The underlying cause of the exception, may be null
135      * @param context  The context used to store the additional information, null uses default implementation
136      */
137     public ContextedException(final String message, final Throwable cause, ExceptionContext context) {
138         super(message, cause);
139         if (context == null) {
140             context = new DefaultExceptionContext();
141         }
142         exceptionContext = context;
143     }
144 
145     /**
146      * Instantiates ContextedException with cause, but without message.
147      * <p>
148      * The context information is stored using a default implementation.
149      *
150      * @param cause  The underlying cause of the exception, may be null
151      */
152     public ContextedException(final Throwable cause) {
153         super(cause);
154         exceptionContext = new DefaultExceptionContext();
155     }
156 
157     /**
158      * Adds information helpful to a developer in diagnosing and correcting the problem.
159      * For the information to be meaningful, the value passed should have a reasonable
160      * toString() implementation.
161      * Different values can be added with the same label multiple times.
162      * <p>
163      * Note: This exception is only serializable if the object added is serializable.
164      * </p>
165      *
166      * @param label  A textual label associated with information, {@code null} not recommended
167      * @param value  information needed to understand exception, may be {@code null}
168      * @return {@code this}, for method chaining, not {@code null}
169      */
170     @Override
171     public ContextedException addContextValue(final String label, final Object value) {
172         exceptionContext.addContextValue(label, value);
173         return this;
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     @Override
180     public List<Pair<String, Object>> getContextEntries() {
181         return this.exceptionContext.getContextEntries();
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
188     public Set<String> getContextLabels() {
189         return exceptionContext.getContextLabels();
190     }
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
196     public List<Object> getContextValues(final String label) {
197         return this.exceptionContext.getContextValues(label);
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
204     public Object getFirstContextValue(final String label) {
205         return this.exceptionContext.getFirstContextValue(label);
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     @Override
212     public String getFormattedExceptionMessage(final String baseMessage) {
213         return exceptionContext.getFormattedExceptionMessage(baseMessage);
214     }
215 
216     /**
217      * Gets the message explaining the exception, including the contextual data.
218      *
219      * @see Throwable#getMessage()
220      * @return The message, never null
221      */
222     @Override
223     public String getMessage() {
224         return getFormattedExceptionMessage(super.getMessage());
225     }
226 
227     /**
228      * Gets the message explaining the exception without the contextual data.
229      *
230      * @see Throwable#getMessage()
231      * @return The message
232      * @since 3.0.1
233      */
234     public String getRawMessage() {
235         return super.getMessage();
236     }
237 
238     /**
239      * Sets information helpful to a developer in diagnosing and correcting the problem.
240      * For the information to be meaningful, the value passed should have a reasonable
241      * toString() implementation.
242      * Any existing values with the same labels are removed before the new one is added.
243      * <p>
244      * Note: This exception is only serializable if the object added as value is serializable.
245      * </p>
246      *
247      * @param label  A textual label associated with information, {@code null} not recommended
248      * @param value  information needed to understand exception, may be {@code null}
249      * @return {@code this}, for method chaining, not {@code null}
250      */
251     @Override
252     public ContextedException setContextValue(final String label, final Object value) {
253         exceptionContext.setContextValue(label, value);
254         return this;
255     }
256 }