ContextedRuntimeException.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.lang3.exception;

  18. import java.util.List;
  19. import java.util.Set;

  20. import org.apache.commons.lang3.tuple.Pair;

  21. /**
  22.  * A runtime exception that provides an easy and safe way to add contextual information.
  23.  * <p>
  24.  * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
  25.  * Frequently what is needed is a select few pieces of local contextual data.
  26.  * Providing this data is tricky however, due to concerns over formatting and nulls.
  27.  * </p><p>
  28.  * The contexted exception approach allows the exception to be created together with a
  29.  * list of context label-value pairs. This additional information is automatically included in
  30.  * the message and printed stack trace.
  31.  * </p><p>
  32.  * A checked version of this exception is provided by ContextedException.
  33.  * </p>
  34.  * <p>
  35.  * To use this class write code as follows:
  36.  * </p>
  37.  * <pre>
  38.  *   try {
  39.  *     ...
  40.  *   } catch (Exception e) {
  41.  *     throw new ContextedRuntimeException("Error posting account transaction", e)
  42.  *          .addContextValue("Account Number", accountNumber)
  43.  *          .addContextValue("Amount Posted", amountPosted)
  44.  *          .addContextValue("Previous Balance", previousBalance);
  45.  *   }
  46.  * }
  47.  * </pre>
  48.  * <p>
  49.  * or improve diagnose data at a higher level:
  50.  * </p>
  51.  * <pre>
  52.  *   try {
  53.  *     ...
  54.  *   } catch (ContextedRuntimeException e) {
  55.  *     throw e.setContextValue("Transaction Id", transactionId);
  56.  *   } catch (Exception e) {
  57.  *     if (e instanceof ExceptionContext) {
  58.  *       e.setContextValue("Transaction Id", transactionId);
  59.  *     }
  60.  *     throw e;
  61.  *   }
  62.  * }
  63.  * </pre>
  64.  * <p>
  65.  * The output in a printStacktrace() (which often is written to a log) would look something like the following:
  66.  * </p>
  67.  * <pre>
  68.  * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
  69.  *  Exception Context:
  70.  *  [1:Account Number=null]
  71.  *  [2:Amount Posted=100.00]
  72.  *  [3:Previous Balance=-2.17]
  73.  *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
  74.  *
  75.  *  ---------------------------------
  76.  *  at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
  77.  *  ..... (rest of trace)
  78.  * </pre>
  79.  *
  80.  * @see ContextedException
  81.  * @since 3.0
  82.  */
  83. public class ContextedRuntimeException extends RuntimeException implements ExceptionContext {

  84.     /** The serialization version. */
  85.     private static final long serialVersionUID = 20110706L;
  86.     /** The context where the data is stored. */
  87.     private final ExceptionContext exceptionContext;

  88.     /**
  89.      * Instantiates ContextedRuntimeException without message or cause.
  90.      * <p>
  91.      * The context information is stored using a default implementation.
  92.      */
  93.     public ContextedRuntimeException() {
  94.         exceptionContext = new DefaultExceptionContext();
  95.     }

  96.     /**
  97.      * Instantiates ContextedRuntimeException with message, but without cause.
  98.      * <p>
  99.      * The context information is stored using a default implementation.
  100.      *
  101.      * @param message  the exception message, may be null
  102.      */
  103.     public ContextedRuntimeException(final String message) {
  104.         super(message);
  105.         exceptionContext = new DefaultExceptionContext();
  106.     }

  107.     /**
  108.      * Instantiates ContextedRuntimeException with cause and message.
  109.      * <p>
  110.      * The context information is stored using a default implementation.
  111.      *
  112.      * @param message  the exception message, may be null
  113.      * @param cause  the underlying cause of the exception, may be null
  114.      */
  115.     public ContextedRuntimeException(final String message, final Throwable cause) {
  116.         super(message, cause);
  117.         exceptionContext = new DefaultExceptionContext();
  118.     }

  119.     /**
  120.      * Instantiates ContextedRuntimeException with cause, message, and ExceptionContext.
  121.      *
  122.      * @param message  the exception message, may be null
  123.      * @param cause  the underlying cause of the exception, may be null
  124.      * @param context  the context used to store the additional information, null uses default implementation
  125.      */
  126.     public ContextedRuntimeException(final String message, final Throwable cause, ExceptionContext context) {
  127.         super(message, cause);
  128.         if (context == null) {
  129.             context = new DefaultExceptionContext();
  130.         }
  131.         exceptionContext = context;
  132.     }

  133.     /**
  134.      * Instantiates ContextedRuntimeException with cause, but without message.
  135.      * <p>
  136.      * The context information is stored using a default implementation.
  137.      *
  138.      * @param cause  the underlying cause of the exception, may be null
  139.      */
  140.     public ContextedRuntimeException(final Throwable cause) {
  141.         super(cause);
  142.         exceptionContext = new DefaultExceptionContext();
  143.     }

  144.     /**
  145.      * Adds information helpful to a developer in diagnosing and correcting the problem.
  146.      * For the information to be meaningful, the value passed should have a reasonable
  147.      * toString() implementation.
  148.      * Different values can be added with the same label multiple times.
  149.      * <p>
  150.      * Note: This exception is only serializable if the object added is serializable.
  151.      * </p>
  152.      *
  153.      * @param label  a textual label associated with information, {@code null} not recommended
  154.      * @param value  information needed to understand exception, may be {@code null}
  155.      * @return {@code this}, for method chaining, not {@code null}
  156.      */
  157.     @Override
  158.     public ContextedRuntimeException addContextValue(final String label, final Object value) {
  159.         exceptionContext.addContextValue(label, value);
  160.         return this;
  161.     }

  162.     /**
  163.      * {@inheritDoc}
  164.      */
  165.     @Override
  166.     public List<Pair<String, Object>> getContextEntries() {
  167.         return this.exceptionContext.getContextEntries();
  168.     }

  169.     /**
  170.      * {@inheritDoc}
  171.      */
  172.     @Override
  173.     public Set<String> getContextLabels() {
  174.         return exceptionContext.getContextLabels();
  175.     }

  176.     /**
  177.      * {@inheritDoc}
  178.      */
  179.     @Override
  180.     public List<Object> getContextValues(final String label) {
  181.         return this.exceptionContext.getContextValues(label);
  182.     }

  183.     /**
  184.      * {@inheritDoc}
  185.      */
  186.     @Override
  187.     public Object getFirstContextValue(final String label) {
  188.         return this.exceptionContext.getFirstContextValue(label);
  189.     }

  190.     /**
  191.      * {@inheritDoc}
  192.      */
  193.     @Override
  194.     public String getFormattedExceptionMessage(final String baseMessage) {
  195.         return exceptionContext.getFormattedExceptionMessage(baseMessage);
  196.     }

  197.     /**
  198.      * Provides the message explaining the exception, including the contextual data.
  199.      *
  200.      * @see Throwable#getMessage()
  201.      * @return the message, never null
  202.      */
  203.     @Override
  204.     public String getMessage() {
  205.         return getFormattedExceptionMessage(super.getMessage());
  206.     }

  207.     /**
  208.      * Provides the message explaining the exception without the contextual data.
  209.      *
  210.      * @see Throwable#getMessage()
  211.      * @return the message
  212.      * @since 3.0.1
  213.      */
  214.     public String getRawMessage() {
  215.         return super.getMessage();
  216.     }

  217.     /**
  218.      * Sets information helpful to a developer in diagnosing and correcting the problem.
  219.      * For the information to be meaningful, the value passed should have a reasonable
  220.      * toString() implementation.
  221.      * Any existing values with the same labels are removed before the new one is added.
  222.      * <p>
  223.      * Note: This exception is only serializable if the object added as value is serializable.
  224.      * </p>
  225.      *
  226.      * @param label  a textual label associated with information, {@code null} not recommended
  227.      * @param value  information needed to understand exception, may be {@code null}
  228.      * @return {@code this}, for method chaining, not {@code null}
  229.      */
  230.     @Override
  231.     public ContextedRuntimeException setContextValue(final String label, final Object value) {
  232.         exceptionContext.setContextValue(label, value);
  233.         return this;
  234.     }

  235. }