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
019/**
020 * {@link TransactionSynchronization} implementation that manages a
021 * {@link ResourceHolder} bound through {@link TransactionSynchronizationManager}.
022 *
023 * @author Juergen Hoeller
024 * @since 2.5.5
025 * @param <H> the resource holder type
026 * @param <K> the resource key type
027 */
028public abstract class ResourceHolderSynchronization<H extends ResourceHolder, K>
029                implements TransactionSynchronization {
030
031        private final H resourceHolder;
032
033        private final K resourceKey;
034
035        private volatile boolean holderActive = true;
036
037
038        /**
039         * Create a new ResourceHolderSynchronization for the given holder.
040         * @param resourceHolder the ResourceHolder to manage
041         * @param resourceKey the key to bind the ResourceHolder for
042         * @see TransactionSynchronizationManager#bindResource
043         */
044        public ResourceHolderSynchronization(H resourceHolder, K resourceKey) {
045                this.resourceHolder = resourceHolder;
046                this.resourceKey = resourceKey;
047        }
048
049
050        @Override
051        public void suspend() {
052                if (this.holderActive) {
053                        TransactionSynchronizationManager.unbindResource(this.resourceKey);
054                }
055        }
056
057        @Override
058        public void resume() {
059                if (this.holderActive) {
060                        TransactionSynchronizationManager.bindResource(this.resourceKey, this.resourceHolder);
061                }
062        }
063
064        @Override
065        public void flush() {
066                flushResource(this.resourceHolder);
067        }
068
069        @Override
070        public void beforeCommit(boolean readOnly) {
071        }
072
073        @Override
074        public void beforeCompletion() {
075                if (shouldUnbindAtCompletion()) {
076                        TransactionSynchronizationManager.unbindResource(this.resourceKey);
077                        this.holderActive = false;
078                        if (shouldReleaseBeforeCompletion()) {
079                                releaseResource(this.resourceHolder, this.resourceKey);
080                        }
081                }
082        }
083
084        @Override
085        public void afterCommit() {
086                if (!shouldReleaseBeforeCompletion()) {
087                        processResourceAfterCommit(this.resourceHolder);
088                }
089        }
090
091        @Override
092        public void afterCompletion(int status) {
093                if (shouldUnbindAtCompletion()) {
094                        boolean releaseNecessary = false;
095                        if (this.holderActive) {
096                                // The thread-bound resource holder might not be available anymore,
097                                // since afterCompletion might get called from a different thread.
098                                this.holderActive = false;
099                                TransactionSynchronizationManager.unbindResourceIfPossible(this.resourceKey);
100                                this.resourceHolder.unbound();
101                                releaseNecessary = true;
102                        }
103                        else {
104                                releaseNecessary = shouldReleaseAfterCompletion(this.resourceHolder);
105                        }
106                        if (releaseNecessary) {
107                                releaseResource(this.resourceHolder, this.resourceKey);
108                        }
109                }
110                else {
111                        // Probably a pre-bound resource...
112                        cleanupResource(this.resourceHolder, this.resourceKey, (status == STATUS_COMMITTED));
113                }
114                this.resourceHolder.reset();
115        }
116
117
118        /**
119         * Return whether this holder should be unbound at completion
120         * (or should rather be left bound to the thread after the transaction).
121         * <p>The default implementation returns {@code true}.
122         */
123        protected boolean shouldUnbindAtCompletion() {
124                return true;
125        }
126
127        /**
128         * Return whether this holder's resource should be released before
129         * transaction completion ({@code true}) or rather after
130         * transaction completion ({@code false}).
131         * <p>Note that resources will only be released when they are
132         * unbound from the thread ({@link #shouldUnbindAtCompletion()}).
133         * <p>The default implementation returns {@code true}.
134         * @see #releaseResource
135         */
136        protected boolean shouldReleaseBeforeCompletion() {
137                return true;
138        }
139
140        /**
141         * Return whether this holder's resource should be released after
142         * transaction completion ({@code true}).
143         * <p>The default implementation returns {@code !shouldReleaseBeforeCompletion()},
144         * releasing after completion if no attempt was made before completion.
145         * @see #releaseResource
146         */
147        protected boolean shouldReleaseAfterCompletion(H resourceHolder) {
148                return !shouldReleaseBeforeCompletion();
149        }
150
151        /**
152         * Flush callback for the given resource holder.
153         * @param resourceHolder the resource holder to flush
154         */
155        protected void flushResource(H resourceHolder) {
156        }
157
158        /**
159         * After-commit callback for the given resource holder.
160         * Only called when the resource hasn't been released yet
161         * ({@link #shouldReleaseBeforeCompletion()}).
162         * @param resourceHolder the resource holder to process
163         */
164        protected void processResourceAfterCommit(H resourceHolder) {
165        }
166
167        /**
168         * Release the given resource (after it has been unbound from the thread).
169         * @param resourceHolder the resource holder to process
170         * @param resourceKey the key that the ResourceHolder was bound for
171         */
172        protected void releaseResource(H resourceHolder, K resourceKey) {
173        }
174
175        /**
176         * Perform a cleanup on the given resource (which is left bound to the thread).
177         * @param resourceHolder the resource holder to process
178         * @param resourceKey the key that the ResourceHolder was bound for
179         * @param committed whether the transaction has committed ({@code true})
180         * or rolled back ({@code false})
181         */
182        protected void cleanupResource(H resourceHolder, K resourceKey, boolean committed) {
183        }
184
185}