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.Set;
020    
021    /**
022     * <p>
023     * A runtime exception that provides an easy and safe way to add contextual information.
024     * </p><p>
025     * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
026     * Frequently what is needed is a select few pieces of local contextual data.
027     * Providing this data is tricky however, due to concerns over formatting and nulls.
028     * </p><p>
029     * The contexted exception approach allows the exception to be created together with a
030     * map of context values. This additional information is automatically included in the
031     * message and printed stack trace.
032     * </p><p>
033     * An checked version of this exception is provided by ContextedException.
034     * </p>
035     * <p>
036     * To use this class write code as follows:
037     * </p>
038     * <pre>
039     *   try {
040     *     ...
041     *   } catch (Exception e) {
042     *     throw new ContextedException("Error posting account transaction", e)
043     *          .addValue("accountNumber", accountNumber)
044     *          .addValue("amountPosted", amountPosted)
045     *          .addValue("previousBalance", previousBalance)
046     *   }
047     * }
048     * </pre>
049     * </p><p>
050     * The output in a printStacktrace() (which often is written to a log) would look something like the following:
051     * <pre>
052     * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
053     *  Exception Context:
054     *  [accountNumber=null]
055     *  [amountPosted=100.00]
056     *  [previousBalance=-2.17]
057     *
058     *  ---------------------------------
059     *  at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
060     *  ..... (rest of trace)
061     * </pre>
062     * </p>
063     * 
064     * @see ContextedException
065     * @author Apache Software Foundation
066     * @author D. Ashmore
067     * @since 3.0
068     */
069    public class ContextedRuntimeException extends RuntimeException implements ExceptionContext {
070    
071        /** The serialization version. */
072        private static final long serialVersionUID = 1459691936045811817L;
073        /** The context where the data is stored. */
074        private final ExceptionContext exceptionContext;
075    
076        /**
077         * Instantiates ContextedRuntimeException without message or cause.
078         * <p>
079         * The context information is stored using a default implementation.
080         */
081        public ContextedRuntimeException() {
082            super();
083            exceptionContext = new DefaultExceptionContext();
084        }
085    
086        /**
087         * Instantiates ContextedRuntimeException with message, but without cause.
088         * <p>
089         * The context information is stored using a default implementation.
090         * 
091         * @param message  the exception message, may be null
092         */
093        public ContextedRuntimeException(String message) {
094            super(message);
095            exceptionContext = new DefaultExceptionContext();
096        }
097    
098        /**
099         * Instantiates ContextedRuntimeException with cause, but without message.
100         * <p>
101         * The context information is stored using a default implementation.
102         * 
103         * @param cause  the underlying cause of the exception, may be null
104         */
105        public ContextedRuntimeException(Throwable cause) {
106            super(cause);
107            exceptionContext = new DefaultExceptionContext();
108        }
109    
110        /**
111         * Instantiates ContextedRuntimeException with cause and message.
112         * <p>
113         * The context information is stored using a default implementation.
114         * 
115         * @param message  the exception message, may be null
116         * @param cause  the underlying cause of the exception, may be null
117         */
118        public ContextedRuntimeException(String message, Throwable cause) {
119            super(message, cause);
120            exceptionContext = new DefaultExceptionContext();
121        }
122    
123        /**
124         * Instantiates ContextedRuntimeException with cause, message, and ExceptionContext.
125         * 
126         * @param message  the exception message, may be null
127         * @param cause  the underlying cause of the exception, may be null
128         * @param context  the context used to store the additional information, null uses default implementation
129         */
130        public ContextedRuntimeException(String message, Throwable cause, ExceptionContext context) {
131            super(message, cause);
132            if (context == null) {
133                context = new DefaultExceptionContext();
134            }
135            exceptionContext = context;
136        }
137    
138        //-----------------------------------------------------------------------
139        /**
140         * Adds information helpful to a developer in diagnosing and correcting
141         * the problem.  For the information to be meaningful, the value passed
142         * should have a reasonable toString() implementation. If the added label
143         * is already available, the label is appended with an index.
144         * <p>
145         * Note: This exception is only serializable if the object added is serializable.
146         * </p>
147         * 
148         * @param label  a textual label associated with information, null not recommended
149         * @param value  information needed to understand exception, may be null
150         * @return this, for method chaining
151         */
152        public ContextedRuntimeException addValue(String label, Object value) {        
153            exceptionContext.addValue(label, value);
154            return this;
155        }
156    
157        /**
158         * Replaces information helpful to a developer in diagnosing and correcting
159         * the problem.  For the information to be meaningful, the value passed
160         * should have a reasonable toString() implementation. If the replaced
161         * label does not yet exist, it is simply added.
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, null not recommended
167         * @param value  information needed to understand exception, may be null
168         * @return this, for method chaining
169         */
170        public ContextedRuntimeException replaceValue(String label, Object value) {        
171            exceptionContext.replaceValue(label, value);
172            return this;
173        }
174    
175        /**
176         * Retrieves a contextual data value associated with the label.
177         * 
178         * @param label  the label to get the contextual value for, may be null
179         * @return the contextual value associated with the label, may be null
180         */
181        public Object getValue(String label) {
182            return exceptionContext.getValue(label);
183        }
184    
185        /**
186         * Retrieves the labels defined in the contextual data.
187         * 
188         * @return the set of labels, never null
189         */
190        public Set<String> getLabelSet() {
191            return exceptionContext.getLabelSet();
192        }
193    
194        /**
195         * Provides the message explaining the exception, including the contextual data.
196         * 
197         * @see java.lang.Throwable#getMessage()
198         * @return the message, never null
199         */
200        @Override
201        public String getMessage(){
202            return getFormattedExceptionMessage(super.getMessage());
203        }
204    
205        /**
206         * {@inheritDoc}
207         */
208        public String getFormattedExceptionMessage(String baseMessage) {
209            return exceptionContext.getFormattedExceptionMessage(baseMessage);
210        }
211    }