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.support;
018
019import java.io.Serializable;
020
021import org.springframework.core.Constants;
022import org.springframework.transaction.TransactionDefinition;
023
024/**
025 * Default implementation of the {@link TransactionDefinition} interface,
026 * offering bean-style configuration and sensible default values
027 * (PROPAGATION_REQUIRED, ISOLATION_DEFAULT, TIMEOUT_DEFAULT, readOnly=false).
028 *
029 * <p>Base class for both {@link TransactionTemplate} and
030 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}.
031 *
032 * @author Juergen Hoeller
033 * @since 08.05.2003
034 */
035@SuppressWarnings("serial")
036public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
037
038        /** Prefix for the propagation constants defined in TransactionDefinition */
039        public static final String PREFIX_PROPAGATION = "PROPAGATION_";
040
041        /** Prefix for the isolation constants defined in TransactionDefinition */
042        public static final String PREFIX_ISOLATION = "ISOLATION_";
043
044        /** Prefix for transaction timeout values in description strings */
045        public static final String PREFIX_TIMEOUT = "timeout_";
046
047        /** Marker for read-only transactions in description strings */
048        public static final String READ_ONLY_MARKER = "readOnly";
049
050
051        /** Constants instance for TransactionDefinition */
052        static final Constants constants = new Constants(TransactionDefinition.class);
053
054        private int propagationBehavior = PROPAGATION_REQUIRED;
055
056        private int isolationLevel = ISOLATION_DEFAULT;
057
058        private int timeout = TIMEOUT_DEFAULT;
059
060        private boolean readOnly = false;
061
062        private String name;
063
064
065        /**
066         * Create a new DefaultTransactionDefinition, with default settings.
067         * Can be modified through bean property setters.
068         * @see #setPropagationBehavior
069         * @see #setIsolationLevel
070         * @see #setTimeout
071         * @see #setReadOnly
072         * @see #setName
073         */
074        public DefaultTransactionDefinition() {
075        }
076
077        /**
078         * Copy constructor. Definition can be modified through bean property setters.
079         * @see #setPropagationBehavior
080         * @see #setIsolationLevel
081         * @see #setTimeout
082         * @see #setReadOnly
083         * @see #setName
084         */
085        public DefaultTransactionDefinition(TransactionDefinition other) {
086                this.propagationBehavior = other.getPropagationBehavior();
087                this.isolationLevel = other.getIsolationLevel();
088                this.timeout = other.getTimeout();
089                this.readOnly = other.isReadOnly();
090                this.name = other.getName();
091        }
092
093        /**
094         * Create a new DefaultTransactionDefinition with the given
095         * propagation behavior. Can be modified through bean property setters.
096         * @param propagationBehavior one of the propagation constants in the
097         * TransactionDefinition interface
098         * @see #setIsolationLevel
099         * @see #setTimeout
100         * @see #setReadOnly
101         */
102        public DefaultTransactionDefinition(int propagationBehavior) {
103                this.propagationBehavior = propagationBehavior;
104        }
105
106
107        /**
108         * Set the propagation behavior by the name of the corresponding constant in
109         * TransactionDefinition, e.g. "PROPAGATION_REQUIRED".
110         * @param constantName name of the constant
111         * @throws IllegalArgumentException if the supplied value is not resolvable
112         * to one of the {@code PROPAGATION_} constants or is {@code null}
113         * @see #setPropagationBehavior
114         * @see #PROPAGATION_REQUIRED
115         */
116        public final void setPropagationBehaviorName(String constantName) throws IllegalArgumentException {
117                if (constantName == null || !constantName.startsWith(PREFIX_PROPAGATION)) {
118                        throw new IllegalArgumentException("Only propagation constants allowed");
119                }
120                setPropagationBehavior(constants.asNumber(constantName).intValue());
121        }
122
123        /**
124         * Set the propagation behavior. Must be one of the propagation constants
125         * in the TransactionDefinition interface. Default is PROPAGATION_REQUIRED.
126         * <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
127         * {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
128         * transactions. Consider switching the "validateExistingTransactions" flag to
129         * "true" on your transaction manager if you'd like isolation level declarations
130         * to get rejected when participating in an existing transaction with a different
131         * isolation level.
132         * <p>Note that a transaction manager that does not support custom isolation levels
133         * will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
134         * @throws IllegalArgumentException if the supplied value is not one of the
135         * {@code PROPAGATION_} constants
136         * @see #PROPAGATION_REQUIRED
137         */
138        public final void setPropagationBehavior(int propagationBehavior) {
139                if (!constants.getValues(PREFIX_PROPAGATION).contains(propagationBehavior)) {
140                        throw new IllegalArgumentException("Only values of propagation constants allowed");
141                }
142                this.propagationBehavior = propagationBehavior;
143        }
144
145        @Override
146        public final int getPropagationBehavior() {
147                return this.propagationBehavior;
148        }
149
150        /**
151         * Set the isolation level by the name of the corresponding constant in
152         * TransactionDefinition, e.g. "ISOLATION_DEFAULT".
153         * @param constantName name of the constant
154         * @throws IllegalArgumentException if the supplied value is not resolvable
155         * to one of the {@code ISOLATION_} constants or is {@code null}
156         * @see #setIsolationLevel
157         * @see #ISOLATION_DEFAULT
158         */
159        public final void setIsolationLevelName(String constantName) throws IllegalArgumentException {
160                if (constantName == null || !constantName.startsWith(PREFIX_ISOLATION)) {
161                        throw new IllegalArgumentException("Only isolation constants allowed");
162                }
163                setIsolationLevel(constants.asNumber(constantName).intValue());
164        }
165
166        /**
167         * Set the isolation level. Must be one of the isolation constants
168         * in the TransactionDefinition interface. Default is ISOLATION_DEFAULT.
169         * <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
170         * {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
171         * transactions. Consider switching the "validateExistingTransactions" flag to
172         * "true" on your transaction manager if you'd like isolation level declarations
173         * to get rejected when participating in an existing transaction with a different
174         * isolation level.
175         * <p>Note that a transaction manager that does not support custom isolation levels
176         * will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
177         * @throws IllegalArgumentException if the supplied value is not one of the
178         * {@code ISOLATION_} constants
179         * @see #ISOLATION_DEFAULT
180         */
181        public final void setIsolationLevel(int isolationLevel) {
182                if (!constants.getValues(PREFIX_ISOLATION).contains(isolationLevel)) {
183                        throw new IllegalArgumentException("Only values of isolation constants allowed");
184                }
185                this.isolationLevel = isolationLevel;
186        }
187
188        @Override
189        public final int getIsolationLevel() {
190                return this.isolationLevel;
191        }
192
193        /**
194         * Set the timeout to apply, as number of seconds.
195         * Default is TIMEOUT_DEFAULT (-1).
196         * <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
197         * {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
198         * transactions.
199         * <p>Note that a transaction manager that does not support timeouts will throw
200         * an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}.
201         * @see #TIMEOUT_DEFAULT
202         */
203        public final void setTimeout(int timeout) {
204                if (timeout < TIMEOUT_DEFAULT) {
205                        throw new IllegalArgumentException("Timeout must be a positive integer or TIMEOUT_DEFAULT");
206                }
207                this.timeout = timeout;
208        }
209
210        @Override
211        public final int getTimeout() {
212                return this.timeout;
213        }
214
215        /**
216         * Set whether to optimize as read-only transaction.
217         * Default is "false".
218         * <p>The read-only flag applies to any transaction context, whether backed
219         * by an actual resource transaction ({@link #PROPAGATION_REQUIRED}/
220         * {@link #PROPAGATION_REQUIRES_NEW}) or operating non-transactionally at
221         * the resource level ({@link #PROPAGATION_SUPPORTS}). In the latter case,
222         * the flag will only apply to managed resources within the application,
223         * such as a Hibernate {@code Session}.
224         * <p>This just serves as a hint for the actual transaction subsystem;
225         * it will <i>not necessarily</i> cause failure of write access attempts.
226         * A transaction manager which cannot interpret the read-only hint will
227         * <i>not</i> throw an exception when asked for a read-only transaction.
228         */
229        public final void setReadOnly(boolean readOnly) {
230                this.readOnly = readOnly;
231        }
232
233        @Override
234        public final boolean isReadOnly() {
235                return this.readOnly;
236        }
237
238        /**
239         * Set the name of this transaction. Default is none.
240         * <p>This will be used as transaction name to be shown in a
241         * transaction monitor, if applicable (for example, WebLogic's).
242         */
243        public final void setName(String name) {
244                this.name = name;
245        }
246
247        @Override
248        public final String getName() {
249                return this.name;
250        }
251
252
253        /**
254         * This implementation compares the {@code toString()} results.
255         * @see #toString()
256         */
257        @Override
258        public boolean equals(Object other) {
259                return (this == other || (other instanceof TransactionDefinition && toString().equals(other.toString())));
260        }
261
262        /**
263         * This implementation returns {@code toString()}'s hash code.
264         * @see #toString()
265         */
266        @Override
267        public int hashCode() {
268                return toString().hashCode();
269        }
270
271        /**
272         * Return an identifying description for this transaction definition.
273         * <p>The format matches the one used by
274         * {@link org.springframework.transaction.interceptor.TransactionAttributeEditor},
275         * to be able to feed {@code toString} results into bean properties of type
276         * {@link org.springframework.transaction.interceptor.TransactionAttribute}.
277         * <p>Has to be overridden in subclasses for correct {@code equals}
278         * and {@code hashCode} behavior. Alternatively, {@link #equals}
279         * and {@link #hashCode} can be overridden themselves.
280         * @see #getDefinitionDescription()
281         * @see org.springframework.transaction.interceptor.TransactionAttributeEditor
282         */
283        @Override
284        public String toString() {
285                return getDefinitionDescription().toString();
286        }
287
288        /**
289         * Return an identifying description for this transaction definition.
290         * <p>Available to subclasses, for inclusion in their {@code toString()} result.
291         */
292        protected final StringBuilder getDefinitionDescription() {
293                StringBuilder result = new StringBuilder();
294                result.append(constants.toCode(this.propagationBehavior, PREFIX_PROPAGATION));
295                result.append(',');
296                result.append(constants.toCode(this.isolationLevel, PREFIX_ISOLATION));
297                if (this.timeout != TIMEOUT_DEFAULT) {
298                        result.append(',');
299                        result.append(PREFIX_TIMEOUT).append(this.timeout);
300                }
301                if (this.readOnly) {
302                        result.append(',');
303                        result.append(READ_ONLY_MARKER);
304                }
305                return result;
306        }
307
308}