001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.lang3.exception;
018
019 import java.util.List;
020 import java.util.Set;
021
022 import org.apache.commons.lang3.tuple.Pair;
023
024 /**
025 * <p>
026 * An exception that provides an easy and safe way to add contextual information.
027 * </p><p>
028 * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
029 * Frequently what is needed is a select few pieces of local contextual data.
030 * Providing this data is tricky however, due to concerns over formatting and nulls.
031 * </p><p>
032 * The contexted exception approach allows the exception to be created together with a
033 * list of context label-value pairs. This additional information is automatically included in
034 * the message and printed stack trace.
035 * </p><p>
036 * An unchecked version of this exception is provided by ContextedRuntimeException.
037 * </p>
038 * <p>
039 * To use this class write code as follows:
040 * </p>
041 * <pre>
042 * try {
043 * ...
044 * } catch (Exception e) {
045 * throw new ContextedException("Error posting account transaction", e)
046 * .addContextValue("Account Number", accountNumber)
047 * .addContextValue("Amount Posted", amountPosted)
048 * .addContextValue("Previous Balance", previousBalance)
049 * }
050 * }
051 * </pre> or improve diagnose data at a higher level:
052 * <pre>
053 * try {
054 * ...
055 * } catch (ContextedException e) {
056 * throw e.setContextValue("Transaction Id", transactionId);
057 * } catch (Exception e) {
058 * if (e instanceof ExceptionContext) {
059 * e.setContextValue("Transaction Id", transactionId);
060 * }
061 * throw e;
062 * }
063 * }
064 * </pre>
065 * </p><p>
066 * The output in a printStacktrace() (which often is written to a log) would look something like the following:
067 * <pre>
068 * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
069 * Exception Context:
070 * [1:Account Number=null]
071 * [2:Amount Posted=100.00]
072 * [3:Previous Balance=-2.17]
073 * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
074 *
075 * ---------------------------------
076 * at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
077 * ..... (rest of trace)
078 * </pre>
079 * </p>
080 *
081 * @see ContextedRuntimeException
082 * @since 3.0
083 */
084 public class ContextedException extends Exception implements ExceptionContext {
085
086 /** The serialization version. */
087 private static final long serialVersionUID = 20110706L;
088 /** The context where the data is stored. */
089 private final ExceptionContext exceptionContext;
090
091 /**
092 * Instantiates ContextedException without message or cause.
093 * <p>
094 * The context information is stored using a default implementation.
095 */
096 public ContextedException() {
097 super();
098 exceptionContext = new DefaultExceptionContext();
099 }
100
101 /**
102 * Instantiates ContextedException with message, but without cause.
103 * <p>
104 * The context information is stored using a default implementation.
105 *
106 * @param message the exception message, may be null
107 */
108 public ContextedException(String message) {
109 super(message);
110 exceptionContext = new DefaultExceptionContext();
111 }
112
113 /**
114 * Instantiates ContextedException with cause, but without message.
115 * <p>
116 * The context information is stored using a default implementation.
117 *
118 * @param cause the underlying cause of the exception, may be null
119 */
120 public ContextedException(Throwable cause) {
121 super(cause);
122 exceptionContext = new DefaultExceptionContext();
123 }
124
125 /**
126 * Instantiates ContextedException with cause and message.
127 * <p>
128 * The context information is stored using a default implementation.
129 *
130 * @param message the exception message, may be null
131 * @param cause the underlying cause of the exception, may be null
132 */
133 public ContextedException(String message, Throwable cause) {
134 super(message, cause);
135 exceptionContext = new DefaultExceptionContext();
136 }
137
138 /**
139 * Instantiates ContextedException with cause, message, and ExceptionContext.
140 *
141 * @param message the exception message, may be null
142 * @param cause the underlying cause of the exception, may be null
143 * @param context the context used to store the additional information, null uses default implementation
144 */
145 public ContextedException(String message, Throwable cause, ExceptionContext context) {
146 super(message, cause);
147 if (context == null) {
148 context = new DefaultExceptionContext();
149 }
150 exceptionContext = context;
151 }
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 public ContextedException addContextValue(String label, Object value) {
168 exceptionContext.addContextValue(label, value);
169 return this;
170 }
171
172 /**
173 * Sets information helpful to a developer in diagnosing and correcting the problem.
174 * For the information to be meaningful, the value passed should have a reasonable
175 * toString() implementation.
176 * Any existing values with the same labels are removed before the new one is added.
177 * <p>
178 * Note: This exception is only serializable if the object added as value is serializable.
179 * </p>
180 *
181 * @param label a textual label associated with information, {@code null} not recommended
182 * @param value information needed to understand exception, may be {@code null}
183 * @return {@code this}, for method chaining, not {@code null}
184 */
185 public ContextedException setContextValue(String label, Object value) {
186 exceptionContext.setContextValue(label, value);
187 return this;
188 }
189
190 /**
191 * {@inheritDoc}
192 */
193 public List<Object> getContextValues(String label) {
194 return this.exceptionContext.getContextValues(label);
195 }
196
197 /**
198 * {@inheritDoc}
199 */
200 public Object getFirstContextValue(String label) {
201 return this.exceptionContext.getFirstContextValue(label);
202 }
203
204 /**
205 * {@inheritDoc}
206 */
207 public List<Pair<String, Object>> getContextEntries() {
208 return this.exceptionContext.getContextEntries();
209 }
210
211 /**
212 * {@inheritDoc}
213 */
214 public Set<String> getContextLabels() {
215 return exceptionContext.getContextLabels();
216 }
217
218 /**
219 * Provides the message explaining the exception, including the contextual data.
220 *
221 * @see java.lang.Throwable#getMessage()
222 * @return the message, never null
223 */
224 @Override
225 public String getMessage(){
226 return getFormattedExceptionMessage(super.getMessage());
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 public String getFormattedExceptionMessage(String baseMessage) {
233 return exceptionContext.getFormattedExceptionMessage(baseMessage);
234 }
235
236 }