001/*
002 * Copyright 2002-2020 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;
020import java.util.ArrayList;
021import java.util.LinkedList;
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.lang.Nullable;
028
029/**
030 * TransactionAttribute implementation that works out whether a given exception
031 * should cause transaction rollback by applying a number of rollback rules,
032 * both positive and negative. If no custom rollback rules apply, this attribute
033 * behaves like DefaultTransactionAttribute (rolling back on runtime exceptions).
034 *
035 * <p>{@link TransactionAttributeEditor} creates objects of this class.
036 *
037 * @author Rod Johnson
038 * @author Juergen Hoeller
039 * @since 09.04.2003
040 * @see TransactionAttributeEditor
041 */
042@SuppressWarnings("serial")
043public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
044
045        /** Prefix for rollback-on-exception rules in description strings. */
046        public static final String PREFIX_ROLLBACK_RULE = "-";
047
048        /** Prefix for commit-on-exception rules in description strings. */
049        public static final String PREFIX_COMMIT_RULE = "+";
050
051
052        /** Static for optimal serializability. */
053        private static final Log logger = LogFactory.getLog(RuleBasedTransactionAttribute.class);
054
055        @Nullable
056        private List<RollbackRuleAttribute> rollbackRules;
057
058
059        /**
060         * Create a new RuleBasedTransactionAttribute, with default settings.
061         * Can be modified through bean property setters.
062         * @see #setPropagationBehavior
063         * @see #setIsolationLevel
064         * @see #setTimeout
065         * @see #setReadOnly
066         * @see #setName
067         * @see #setRollbackRules
068         */
069        public RuleBasedTransactionAttribute() {
070                super();
071        }
072
073        /**
074         * Copy constructor. Definition can be modified through bean property setters.
075         * @see #setPropagationBehavior
076         * @see #setIsolationLevel
077         * @see #setTimeout
078         * @see #setReadOnly
079         * @see #setName
080         * @see #setRollbackRules
081         */
082        public RuleBasedTransactionAttribute(RuleBasedTransactionAttribute other) {
083                super(other);
084                this.rollbackRules = (other.rollbackRules != null ? new ArrayList<>(other.rollbackRules) : null);
085        }
086
087        /**
088         * Create a new DefaultTransactionAttribute with the given
089         * propagation behavior. Can be modified through bean property setters.
090         * @param propagationBehavior one of the propagation constants in the
091         * TransactionDefinition interface
092         * @param rollbackRules the list of RollbackRuleAttributes to apply
093         * @see #setIsolationLevel
094         * @see #setTimeout
095         * @see #setReadOnly
096         */
097        public RuleBasedTransactionAttribute(int propagationBehavior, List<RollbackRuleAttribute> rollbackRules) {
098                super(propagationBehavior);
099                this.rollbackRules = rollbackRules;
100        }
101
102
103        /**
104         * Set the list of {@code RollbackRuleAttribute} objects
105         * (and/or {@code NoRollbackRuleAttribute} objects) to apply.
106         * @see RollbackRuleAttribute
107         * @see NoRollbackRuleAttribute
108         */
109        public void setRollbackRules(List<RollbackRuleAttribute> rollbackRules) {
110                this.rollbackRules = rollbackRules;
111        }
112
113        /**
114         * Return the list of {@code RollbackRuleAttribute} objects
115         * (never {@code null}).
116         */
117        public List<RollbackRuleAttribute> getRollbackRules() {
118                if (this.rollbackRules == null) {
119                        this.rollbackRules = new LinkedList<>();
120                }
121                return this.rollbackRules;
122        }
123
124
125        /**
126         * Winning rule is the shallowest rule (that is, the closest in the
127         * inheritance hierarchy to the exception). If no rule applies (-1),
128         * return false.
129         * @see TransactionAttribute#rollbackOn(java.lang.Throwable)
130         */
131        @Override
132        public boolean rollbackOn(Throwable ex) {
133                if (logger.isTraceEnabled()) {
134                        logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
135                }
136
137                RollbackRuleAttribute winner = null;
138                int deepest = Integer.MAX_VALUE;
139
140                if (this.rollbackRules != null) {
141                        for (RollbackRuleAttribute rule : this.rollbackRules) {
142                                int depth = rule.getDepth(ex);
143                                if (depth >= 0 && depth < deepest) {
144                                        deepest = depth;
145                                        winner = rule;
146                                }
147                        }
148                }
149
150                if (logger.isTraceEnabled()) {
151                        logger.trace("Winning rollback rule is: " + winner);
152                }
153
154                // User superclass behavior (rollback on unchecked) if no rule matches.
155                if (winner == null) {
156                        logger.trace("No relevant rollback rule found: applying default rules");
157                        return super.rollbackOn(ex);
158                }
159
160                return !(winner instanceof NoRollbackRuleAttribute);
161        }
162
163
164        @Override
165        public String toString() {
166                StringBuilder result = getAttributeDescription();
167                if (this.rollbackRules != null) {
168                        for (RollbackRuleAttribute rule : this.rollbackRules) {
169                                String sign = (rule instanceof NoRollbackRuleAttribute ? PREFIX_COMMIT_RULE : PREFIX_ROLLBACK_RULE);
170                                result.append(',').append(sign).append(rule.getExceptionName());
171                        }
172                }
173                return result.toString();
174        }
175
176}