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.support;
018
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.Map;
022
023import org.springframework.beans.factory.ObjectFactory;
024import org.springframework.beans.factory.config.Scope;
025import org.springframework.lang.Nullable;
026
027/**
028 * A simple transaction-backed {@link Scope} implementation, delegating to
029 * {@link TransactionSynchronizationManager}'s resource binding mechanism.
030 *
031 * <p><b>NOTE:</b> Like {@link org.springframework.context.support.SimpleThreadScope},
032 * this transaction scope is not registered by default in common contexts. Instead,
033 * you need to explicitly assign it to a scope key in your setup, either through
034 * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope}
035 * or through a {@link org.springframework.beans.factory.config.CustomScopeConfigurer} bean.
036 *
037 * @author Juergen Hoeller
038 * @since 4.2
039 * @see org.springframework.context.support.SimpleThreadScope
040 * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope
041 * @see org.springframework.beans.factory.config.CustomScopeConfigurer
042 */
043public class SimpleTransactionScope implements Scope {
044
045        @Override
046        public Object get(String name, ObjectFactory<?> objectFactory) {
047                ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
048                if (scopedObjects == null) {
049                        scopedObjects = new ScopedObjectsHolder();
050                        TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
051                        TransactionSynchronizationManager.bindResource(this, scopedObjects);
052                }
053                // NOTE: Do NOT modify the following to use Map::computeIfAbsent. For details,
054                // see https://github.com/spring-projects/spring-framework/issues/25801.
055                Object scopedObject = scopedObjects.scopedInstances.get(name);
056                if (scopedObject == null) {
057                        scopedObject = objectFactory.getObject();
058                        scopedObjects.scopedInstances.put(name, scopedObject);
059                }
060                return scopedObject;
061        }
062
063        @Override
064        @Nullable
065        public Object remove(String name) {
066                ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
067                if (scopedObjects != null) {
068                        scopedObjects.destructionCallbacks.remove(name);
069                        return scopedObjects.scopedInstances.remove(name);
070                }
071                else {
072                        return null;
073                }
074        }
075
076        @Override
077        public void registerDestructionCallback(String name, Runnable callback) {
078                ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
079                if (scopedObjects != null) {
080                        scopedObjects.destructionCallbacks.put(name, callback);
081                }
082        }
083
084        @Override
085        @Nullable
086        public Object resolveContextualObject(String key) {
087                return null;
088        }
089
090        @Override
091        @Nullable
092        public String getConversationId() {
093                return TransactionSynchronizationManager.getCurrentTransactionName();
094        }
095
096
097        /**
098         * Holder for scoped objects.
099         */
100        static class ScopedObjectsHolder {
101
102                final Map<String, Object> scopedInstances = new HashMap<>();
103
104                final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>();
105        }
106
107
108        private class CleanupSynchronization extends TransactionSynchronizationAdapter {
109
110                private final ScopedObjectsHolder scopedObjects;
111
112                public CleanupSynchronization(ScopedObjectsHolder scopedObjects) {
113                        this.scopedObjects = scopedObjects;
114                }
115
116                @Override
117                public void suspend() {
118                        TransactionSynchronizationManager.unbindResource(SimpleTransactionScope.this);
119                }
120
121                @Override
122                public void resume() {
123                        TransactionSynchronizationManager.bindResource(SimpleTransactionScope.this, this.scopedObjects);
124                }
125
126                @Override
127                public void afterCompletion(int status) {
128                        TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this);
129                        for (Runnable callback : this.scopedObjects.destructionCallbacks.values()) {
130                                callback.run();
131                        }
132                        this.scopedObjects.destructionCallbacks.clear();
133                        this.scopedObjects.scopedInstances.clear();
134                }
135        }
136
137}