    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     * An 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 unchecked version of this exception is provided by ContextedRuntimeException.
    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.ContextedException: 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.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
    060     *  ..... (rest of trace)
    061     * </pre>
    062     * </p>
    063     *
    064     * @see ContextedRuntimeException
    065     * @author Apache Software Foundation
    066     * @author D. Ashmore
    067     * @since 3.0
    068     */
    069    public class ContextedException extends Exception implements ExceptionContext {
    070
    071        /** The serialization version. */
    072        private static final long serialVersionUID = 8940917952810290164L;
    073        /** The context where the data is stored. */
    074        private final ExceptionContext exceptionContext;
    075
    076        /**
    077         * Instantiates ContextedException without message or cause.
    078         * <p>
    079         * The context information is stored using a default implementation.
    080         */
    081        public ContextedException() {
    082            super();
    083            exceptionContext = new DefaultExceptionContext();
    084        }
    085
    086        /**
    087         * Instantiates ContextedException 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 ContextedException(String message) {
    094            super(message);
    095            exceptionContext = new DefaultExceptionContext();
    096        }
    097
    098        /**
    099         * Instantiates ContextedException 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 ContextedException(Throwable cause) {
    106            super(cause);
    107            exceptionContext = new DefaultExceptionContext();
    108        }
    109
    110        /**
    111         * Instantiates ContextedException 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 ContextedException(String message, Throwable cause) {
    119            super(message, cause);
    120            exceptionContext = new DefaultExceptionContext();
    121        }
    122
    123        /**
    124         * Instantiates ContextedException 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 ContextedException(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 ContextedException 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 ContextedException 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    }