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 * {@inheritDoc} 231 */ 232 public String getFormattedExceptionMessage(String baseMessage) { 233 return exceptionContext.getFormattedExceptionMessage(baseMessage); 234 } 235 236 }