1 /*
2  * Copyright 2008-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.data.repository.core.support;
17
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20
21 import org.springframework.aop.framework.ProxyFactory;
22 import org.springframework.beans.factory.BeanFactory;
23 import org.springframework.beans.factory.ListableBeanFactory;
24 import org.springframework.core.BridgeMethodResolver;
25 import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor;
26 import org.springframework.data.repository.core.RepositoryInformation;
27 import org.springframework.data.util.ProxyUtils;
28 import org.springframework.lang.Nullable;
29 import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
30 import org.springframework.transaction.interceptor.TransactionAttribute;
31 import org.springframework.transaction.interceptor.TransactionInterceptor;
32 import org.springframework.util.Assert;
33 import org.springframework.util.ClassUtils;
34
35 /**
36  * {@link RepositoryProxyPostProcessor} to add transactional behaviour to repository proxies. Adds a
37  * {@link PersistenceExceptionTranslationInterceptor} as well as an annotation based {@link TransactionInterceptor} to
38  * the proxy.
39  *
40  * @author Oliver Gierke
41  * @author Christoph Strobl
42  * @author Mark Paluch
43  */

44 class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostProcessor {
45
46     private final BeanFactory beanFactory;
47     private final String transactionManagerName;
48     private final boolean enableDefaultTransactions;
49
50     /**
51      * Creates a new {@link TransactionalRepositoryProxyPostProcessor} using the given {@link ListableBeanFactory} and
52      * transaction manager bean name.
53      *
54      * @param beanFactory must not be {@literal null}.
55      * @param transactionManagerName must not be {@literal null} or empty.
56      * @param enableDefaultTransaction
57      */

58     public TransactionalRepositoryProxyPostProcessor(ListableBeanFactory beanFactory, String transactionManagerName,
59             boolean enableDefaultTransaction) {
60
61         Assert.notNull(beanFactory, "BeanFactory must not be null!");
62         Assert.notNull(transactionManagerName, "TransactionManagerName must not be null!");
63
64         this.beanFactory = beanFactory;
65         this.transactionManagerName = transactionManagerName;
66         this.enableDefaultTransactions = enableDefaultTransaction;
67     }
68
69     /*
70      * (non-Javadoc)
71      * @see org.springframework.data.repository.core.support.RepositoryProxyPostProcessor#postProcess(org.springframework.aop.framework.ProxyFactory, org.springframework.data.repository.core.RepositoryInformation)
72      */

73     public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
74
75         TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
76         transactionInterceptor.setTransactionAttributeSource(
77                 new RepositoryAnnotationTransactionAttributeSource(repositoryInformation, enableDefaultTransactions));
78         transactionInterceptor.setTransactionManagerBeanName(transactionManagerName);
79         transactionInterceptor.setBeanFactory(beanFactory);
80         transactionInterceptor.afterPropertiesSet();
81
82         factory.addAdvice(transactionInterceptor);
83     }
84
85     /**
86      * Custom implementation of {@link AnnotationTransactionAttributeSource} that that slightly modify the algorithm
87      * transaction configuration is discovered.
88      * <p>
89      * The original Spring implementation favors the implementation class' transaction configuration over one declared at
90      * an interface. As we need to provide the capability to override transaction configuration of the implementation at
91      * the interface level we pretty much invert this logic to inspect the originally invoked method first before digging
92      * down into the implementation class.
93      *
94      * @author Oliver Drotbohm
95      * @author Mark Paluch
96      */

97     static class RepositoryAnnotationTransactionAttributeSource extends AnnotationTransactionAttributeSource {
98
99         private static final long serialVersionUID = 7229616838812819438L;
100
101         private final RepositoryInformation repositoryInformation;
102         private final boolean enableDefaultTransactions;
103
104         /**
105          * Create a default CustomAnnotationTransactionAttributeSource, supporting public methods that carry the
106          * {@code Transactional} annotation or the EJB3 {@link javax.ejb.TransactionAttribute} annotation.
107          */

108         public RepositoryAnnotationTransactionAttributeSource(RepositoryInformation repositoryInformation,
109                 boolean enableDefaultTransactions) {
110
111             super(true);
112
113             Assert.notNull(repositoryInformation, "RepositoryInformation must not be null!");
114
115             this.enableDefaultTransactions = enableDefaultTransactions;
116             this.repositoryInformation = repositoryInformation;
117         }
118
119         /*
120          * (non-Javadoc)
121          * @see org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute(java.lang.reflect.Method, java.lang.Class)
122          */

123         @Override
124         @Nullable
125         protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
126
127             // Don't allow no-public methods as required.
128             if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
129                 return null;
130             }
131
132             // Ignore CGLIB subclasses - introspect the actual user class.
133             Class<?> userClass = targetClass == null ? targetClass : ProxyUtils.getUserClass(targetClass);
134
135             // The method may be on an interface, but we need attributes from the target class.
136             // If the target class is null, the method will be unchanged.
137             Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
138
139             // If we are dealing with method with generic parameters, find the original method.
140             specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
141
142             TransactionAttribute txAtt = null;
143
144             if (specificMethod != method) {
145
146                 // Fallback is to look at the original method.
147                 txAtt = findTransactionAttribute(method);
148
149                 if (txAtt != null) {
150                     return txAtt;
151                 }
152
153                 // Last fallback is the class of the original method.
154                 txAtt = findTransactionAttribute(method.getDeclaringClass());
155
156                 if (txAtt != null || !enableDefaultTransactions) {
157                     return txAtt;
158                 }
159             }
160
161             // First try is the method in the target class.
162             txAtt = findTransactionAttribute(specificMethod);
163
164             if (txAtt != null) {
165                 return txAtt;
166             }
167
168             // Second try is the transaction attribute on the target class.
169             txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());
170
171             if (txAtt != null) {
172                 return txAtt;
173             }
174
175             if (!enableDefaultTransactions) {
176                 return null;
177             }
178
179             // Fallback to implementation class transaction settings of nothing found
180             // return findTransactionAttribute(method);
181             Method targetClassMethod = repositoryInformation.getTargetClassMethod(method);
182
183             if (targetClassMethod.equals(method)) {
184                 return null;
185             }
186
187             txAtt = findTransactionAttribute(targetClassMethod);
188
189             if (txAtt != null) {
190                 return txAtt;
191             }
192
193             txAtt = findTransactionAttribute(targetClassMethod.getDeclaringClass());
194
195             if (txAtt != null) {
196                 return txAtt;
197             }
198
199             return null;
200         }
201     }
202 }
203