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