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