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 *      https://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 */
017package org.apache.commons.lang3.exception;
018
019import java.util.List;
020import java.util.Set;
021
022import org.apache.commons.lang3.tuple.Pair;
023
024/**
025 * A runtime exception that provides an easy and safe way to add contextual information.
026 * <p>
027 * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
028 * Frequently what is needed is a select few pieces of local contextual data.
029 * Providing this data is tricky however, due to concerns over formatting and nulls.
030 * </p>
031 * <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>
036 * <p>
037 * A checked version of this exception is provided by ContextedException.
038 * </p>
039 * <p>
040 * To use this class write code as follows:
041 * </p>
042 * <pre>
043 *   try {
044 *     ...
045 *   } catch (Exception e) {
046 *     throw new ContextedRuntimeException("Error posting account transaction", e)
047 *          .addContextValue("Account Number", accountNumber)
048 *          .addContextValue("Amount Posted", amountPosted)
049 *          .addContextValue("Previous Balance", previousBalance);
050 *   }
051 * }
052 * </pre>
053 * <p>
054 * or improve diagnose data at a higher level:
055 * </p>
056 * <pre>
057 *   try {
058 *     ...
059 *   } catch (ContextedRuntimeException e) {
060 *     throw e.setContextValue("Transaction Id", transactionId);
061 *   } catch (Exception e) {
062 *     if (e instanceof ExceptionContext) {
063 *       e.setContextValue("Transaction Id", transactionId);
064 *     }
065 *     throw e;
066 *   }
067 * }
068 * </pre>
069 * <p>
070 * The output in a printStacktrace() (which often is written to a log) would look something like the following:
071 * </p>
072 * <pre>
073 * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
074 *  Exception Context:
075 *  [1:Account Number=null]
076 *  [2:Amount Posted=100.00]
077 *  [3:Previous Balance=-2.17]
078 *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
079 *
080 *  ---------------------------------
081 *  at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
082 *  ..... (rest of trace)
083 * </pre>
084 *
085 * @see ContextedException
086 * @since 3.0
087 */
088public class ContextedRuntimeException extends RuntimeException implements ExceptionContext {
089
090    /** The serialization version. */
091    private static final long serialVersionUID = 20110706L;
092
093    /** The context where the data is stored. */
094    private final ExceptionContext exceptionContext;
095
096    /**
097     * Instantiates ContextedRuntimeException without message or cause.
098     * <p>
099     * The context information is stored using a default implementation.
100     */
101    public ContextedRuntimeException() {
102        exceptionContext = new DefaultExceptionContext();
103    }
104
105    /**
106     * Instantiates ContextedRuntimeException 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 ContextedRuntimeException(final String message) {
113        super(message);
114        exceptionContext = new DefaultExceptionContext();
115    }
116
117    /**
118     * Instantiates ContextedRuntimeException 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 ContextedRuntimeException(final String message, final Throwable cause) {
126        super(message, cause);
127        exceptionContext = new DefaultExceptionContext();
128    }
129
130    /**
131     * Instantiates ContextedRuntimeException 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 ContextedRuntimeException(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 ContextedRuntimeException 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 ContextedRuntimeException(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 ContextedRuntimeException 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 ContextedRuntimeException setContextValue(final String label, final Object value) {
253        exceptionContext.setContextValue(label, value);
254        return this;
255    }
256
257}