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         * Provides the message explaining the exception without the contextual data.
231         * 
232         * @see java.lang.Throwable#getMessage()
233         * @return the message
234         * @since 3.0.1
235         */
236        public String getRawMessage() {
237            return super.getMessage();
238        }
239    
240        /**
241         * {@inheritDoc}
242         */
243        public String getFormattedExceptionMessage(String baseMessage) {
244            return exceptionContext.getFormattedExceptionMessage(baseMessage);
245        }
246    }