001/* 002 * Copyright 2002-2015 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.transaction.interceptor; 018 019import java.io.Serializable; 020 021import org.springframework.util.Assert; 022 023/** 024 * Rule determining whether or not a given exception (and any subclasses) 025 * should cause a rollback. 026 * 027 * <p>Multiple such rules can be applied to determine whether a transaction 028 * should commit or rollback after an exception has been thrown. 029 * 030 * @author Rod Johnson 031 * @since 09.04.2003 032 * @see NoRollbackRuleAttribute 033 */ 034@SuppressWarnings("serial") 035public class RollbackRuleAttribute implements Serializable{ 036 037 /** 038 * The {@link RollbackRuleAttribute rollback rule} for 039 * {@link RuntimeException RuntimeExceptions}. 040 */ 041 public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS = 042 new RollbackRuleAttribute(RuntimeException.class); 043 044 045 /** 046 * Could hold exception, resolving class name but would always require FQN. 047 * This way does multiple string comparisons, but how often do we decide 048 * whether to roll back a transaction following an exception? 049 */ 050 private final String exceptionName; 051 052 053 /** 054 * Create a new instance of the {@code RollbackRuleAttribute} class. 055 * <p>This is the preferred way to construct a rollback rule that matches 056 * the supplied {@link Exception} class (and subclasses). 057 * @param clazz throwable class; must be {@link Throwable} or a subclass 058 * of {@code Throwable} 059 * @throws IllegalArgumentException if the supplied {@code clazz} is 060 * not a {@code Throwable} type or is {@code null} 061 */ 062 public RollbackRuleAttribute(Class<?> clazz) { 063 Assert.notNull(clazz, "'clazz' cannot be null"); 064 if (!Throwable.class.isAssignableFrom(clazz)) { 065 throw new IllegalArgumentException( 066 "Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable"); 067 } 068 this.exceptionName = clazz.getName(); 069 } 070 071 /** 072 * Create a new instance of the {@code RollbackRuleAttribute} class 073 * for the given {@code exceptionName}. 074 * <p>This can be a substring, with no wildcard support at present. A value 075 * of "ServletException" would match 076 * {@code javax.servlet.ServletException} and subclasses, for example. 077 * <p><b>NB:</b> Consider carefully how specific the pattern is, and 078 * whether to include package information (which is not mandatory). For 079 * example, "Exception" will match nearly anything, and will probably hide 080 * other rules. "java.lang.Exception" would be correct if "Exception" was 081 * meant to define a rule for all checked exceptions. With more unusual 082 * exception names such as "BaseBusinessException" there's no need to use a 083 * fully package-qualified name. 084 * @param exceptionName the exception name pattern; can also be a fully 085 * package-qualified class name 086 * @throws IllegalArgumentException if the supplied 087 * {@code exceptionName} is {@code null} or empty 088 */ 089 public RollbackRuleAttribute(String exceptionName) { 090 Assert.hasText(exceptionName, "'exceptionName' cannot be null or empty"); 091 this.exceptionName = exceptionName; 092 } 093 094 095 /** 096 * Return the pattern for the exception name. 097 */ 098 public String getExceptionName() { 099 return exceptionName; 100 } 101 102 /** 103 * Return the depth of the superclass matching. 104 * <p>{@code 0} means {@code ex} matches exactly. Returns 105 * {@code -1} if there is no match. Otherwise, returns depth with the 106 * lowest depth winning. 107 */ 108 public int getDepth(Throwable ex) { 109 return getDepth(ex.getClass(), 0); 110 } 111 112 113 private int getDepth(Class<?> exceptionClass, int depth) { 114 if (exceptionClass.getName().contains(this.exceptionName)) { 115 // Found it! 116 return depth; 117 } 118 // If we've gone as far as we can go and haven't found it... 119 if (exceptionClass == Throwable.class) { 120 return -1; 121 } 122 return getDepth(exceptionClass.getSuperclass(), depth + 1); 123 } 124 125 126 @Override 127 public boolean equals(Object other) { 128 if (this == other) { 129 return true; 130 } 131 if (!(other instanceof RollbackRuleAttribute)) { 132 return false; 133 } 134 RollbackRuleAttribute rhs = (RollbackRuleAttribute) other; 135 return this.exceptionName.equals(rhs.exceptionName); 136 } 137 138 @Override 139 public int hashCode() { 140 return this.exceptionName.hashCode(); 141 } 142 143 @Override 144 public String toString() { 145 return "RollbackRuleAttribute with pattern [" + this.exceptionName + "]"; 146 } 147 148}