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