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}