    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    }