• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *      http://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><p>
31  * The contexted exception approach allows the exception to be created together with a
32  * list of context label-value pairs. This additional information is automatically included in
33  * the message and printed stack trace.
34  * </p><p>
35  * An unchecked version of this exception is provided by ContextedRuntimeException.
36  * </p>
37  * <p>
38  * To use this class write code as follows:
39  * </p>
40  * <pre>
41  *   try {
42  *     ...
43  *   } catch (Exception e) {
44  *     throw new ContextedException("Error posting account transaction", e)
45  *          .addContextValue("Account Number", accountNumber)
46  *          .addContextValue("Amount Posted", amountPosted)
47  *          .addContextValue("Previous Balance", previousBalance);
48  *   }
49  * }
50  * </pre>
51  * <p>
52  * or improve diagnose data at a higher level:
53  * </p>
54  * <pre>
55  *   try {
56  *     ...
57  *   } catch (ContextedException e) {
58  *     throw e.setContextValue("Transaction Id", transactionId);
59  *   } catch (Exception e) {
60  *     if (e instanceof ExceptionContext) {
61  *       e.setContextValue("Transaction Id", transactionId);
62  *     }
63  *     throw e;
64  *   }
65  * }
66  * </pre>
67  * <p>
68  * The output in a printStacktrace() (which often is written to a log) would look something like the following:
69  * </p>
70  * <pre>
71  * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
72  *  Exception Context:
73  *  [1:Account Number=null]
74  *  [2:Amount Posted=100.00]
75  *  [3:Previous Balance=-2.17]
76  *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
77  *
78  *  ---------------------------------
79  *  at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
80  *  ..... (rest of trace)
81  * </pre>
82  *
83  * @see ContextedRuntimeException
84  * @since 3.0
85  */
86 public class ContextedException extends Exception implements ExceptionContext {
87 
88     /** The serialization version. */
89     private static final long serialVersionUID = 20110706L;
90     /** The context where the data is stored. */
91     private final ExceptionContext exceptionContext;
92 
93     /**
94      * Instantiates ContextedException without message or cause.
95      * <p>
96      * The context information is stored using a default implementation.
97      */
ContextedException()98     public ContextedException() {
99         exceptionContext = new DefaultExceptionContext();
100     }
101 
102     /**
103      * Instantiates ContextedException with message, but without cause.
104      * <p>
105      * The context information is stored using a default implementation.
106      *
107      * @param message  the exception message, may be null
108      */
ContextedException(final String message)109     public ContextedException(final String message) {
110         super(message);
111         exceptionContext = new DefaultExceptionContext();
112     }
113 
114     /**
115      * Instantiates ContextedException with cause, but without message.
116      * <p>
117      * The context information is stored using a default implementation.
118      *
119      * @param cause  the underlying cause of the exception, may be null
120      */
ContextedException(final Throwable cause)121     public ContextedException(final Throwable cause) {
122         super(cause);
123         exceptionContext = new DefaultExceptionContext();
124     }
125 
126     /**
127      * Instantiates ContextedException with cause and message.
128      * <p>
129      * The context information is stored using a default implementation.
130      *
131      * @param message  the exception message, may be null
132      * @param cause  the underlying cause of the exception, may be null
133      */
ContextedException(final String message, final Throwable cause)134     public ContextedException(final String message, final Throwable cause) {
135         super(message, cause);
136         exceptionContext = new DefaultExceptionContext();
137     }
138 
139     /**
140      * Instantiates ContextedException with cause, message, and ExceptionContext.
141      *
142      * @param message  the exception message, may be null
143      * @param cause  the underlying cause of the exception, may be null
144      * @param context  the context used to store the additional information, null uses default implementation
145      */
ContextedException(final String message, final Throwable cause, ExceptionContext context)146     public ContextedException(final String message, final Throwable cause, ExceptionContext context) {
147         super(message, cause);
148         if (context == null) {
149             context = new DefaultExceptionContext();
150         }
151         exceptionContext = context;
152     }
153 
154     /**
155      * Adds information helpful to a developer in diagnosing and correcting the problem.
156      * For the information to be meaningful, the value passed should have a reasonable
157      * toString() implementation.
158      * Different values can be added with the same label multiple times.
159      * <p>
160      * Note: This exception is only serializable if the object added is serializable.
161      * </p>
162      *
163      * @param label  a textual label associated with information, {@code null} not recommended
164      * @param value  information needed to understand exception, may be {@code null}
165      * @return {@code this}, for method chaining, not {@code null}
166      */
167     @Override
addContextValue(final String label, final Object value)168     public ContextedException addContextValue(final String label, final Object value) {
169         exceptionContext.addContextValue(label, value);
170         return this;
171     }
172 
173     /**
174      * Sets information helpful to a developer in diagnosing and correcting the problem.
175      * For the information to be meaningful, the value passed should have a reasonable
176      * toString() implementation.
177      * Any existing values with the same labels are removed before the new one is added.
178      * <p>
179      * Note: This exception is only serializable if the object added as value is serializable.
180      * </p>
181      *
182      * @param label  a textual label associated with information, {@code null} not recommended
183      * @param value  information needed to understand exception, may be {@code null}
184      * @return {@code this}, for method chaining, not {@code null}
185      */
186     @Override
setContextValue(final String label, final Object value)187     public ContextedException setContextValue(final String label, final Object value) {
188         exceptionContext.setContextValue(label, value);
189         return this;
190     }
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
getContextValues(final String label)196     public List<Object> getContextValues(final String label) {
197         return this.exceptionContext.getContextValues(label);
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
getFirstContextValue(final String label)204     public Object getFirstContextValue(final String label) {
205         return this.exceptionContext.getFirstContextValue(label);
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     @Override
getContextEntries()212     public List<Pair<String, Object>> getContextEntries() {
213         return this.exceptionContext.getContextEntries();
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
getContextLabels()220     public Set<String> getContextLabels() {
221         return exceptionContext.getContextLabels();
222     }
223 
224     /**
225      * Provides the message explaining the exception, including the contextual data.
226      *
227      * @see Throwable#getMessage()
228      * @return the message, never null
229      */
230     @Override
getMessage()231     public String getMessage() {
232         return getFormattedExceptionMessage(super.getMessage());
233     }
234 
235     /**
236      * Provides the message explaining the exception without the contextual data.
237      *
238      * @see Throwable#getMessage()
239      * @return the message
240      * @since 3.0.1
241      */
getRawMessage()242     public String getRawMessage() {
243         return super.getMessage();
244     }
245 
246     /**
247      * {@inheritDoc}
248      */
249     @Override
getFormattedExceptionMessage(final String baseMessage)250     public String getFormattedExceptionMessage(final String baseMessage) {
251         return exceptionContext.getFormattedExceptionMessage(baseMessage);
252     }
253 }
254