1 /*
2  * Copyright 2012-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.jpa.repository.config;
17
18 import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*;
19
20 import lombok.experimental.UtilityClass;
21
22 import java.lang.annotation.Annotation;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.LinkedHashSet;
27 import java.util.Locale;
28 import java.util.Optional;
29 import java.util.Set;
30
31 import javax.persistence.Entity;
32 import javax.persistence.MappedSuperclass;
33 import javax.persistence.PersistenceContext;
34 import javax.persistence.PersistenceUnit;
35
36 import org.springframework.beans.factory.support.AbstractBeanDefinition;
37 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
38 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
39 import org.springframework.beans.factory.support.RootBeanDefinition;
40 import org.springframework.context.annotation.AnnotationConfigUtils;
41 import org.springframework.core.annotation.AnnotationAttributes;
42 import org.springframework.core.io.ResourceLoader;
43 import org.springframework.dao.DataAccessException;
44 import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
45 import org.springframework.data.jpa.repository.JpaRepository;
46 import org.springframework.data.jpa.repository.support.DefaultJpaContext;
47 import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor;
48 import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension;
49 import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
50 import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
51 import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
52 import org.springframework.data.repository.config.RepositoryConfigurationSource;
53 import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
54 import org.springframework.lang.Nullable;
55 import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
56 import org.springframework.util.ClassUtils;
57 import org.springframework.util.StringUtils;
58
59 /**
60  * JPA specific configuration extension parsing custom attributes from the XML namespace and
61  * {@link EnableJpaRepositories} annotation. Also, it registers bean definitions for a
62  * {@link PersistenceAnnotationBeanPostProcessor} (to trigger injection into {@link PersistenceContext}/
63  * {@link PersistenceUnit} annotated properties and methods) as well as
64  * {@link PersistenceExceptionTranslationPostProcessor} to enable exception translation of persistence specific
65  * exceptions into Spring's {@link DataAccessException} hierarchy.
66  *
67  * @author Oliver Gierke
68  * @author Eberhard Wolff
69  * @author Gil Markham
70  * @author Thomas Darimont
71  * @author Christoph Strobl
72  * @author Mark Paluch
73  */

74 public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
75
76     private static final Class<?> PAB_POST_PROCESSOR = PersistenceAnnotationBeanPostProcessor.class;
77     private static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
78     private static final String ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE = "enableDefaultTransactions";
79     private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME = "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup";
80     private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter";
81
82     /*
83      * (non-Javadoc)
84      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName()
85      */

86     @Override
87     public String getModuleName() {
88         return "JPA";
89     }
90
91     /*
92      * (non-Javadoc)
93      * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryBeanClassName()
94      */

95     @Override
96     public String getRepositoryFactoryBeanClassName() {
97         return JpaRepositoryFactoryBean.class.getName();
98     }
99
100     /*
101      * (non-Javadoc)
102      * @see org.springframework.data.repository.config14.RepositoryConfigurationExtensionSupport#getModulePrefix()
103      */

104     @Override
105     protected String getModulePrefix() {
106         return getModuleName().toLowerCase(Locale.US);
107     }
108
109     /*
110      * (non-Javadoc)
111      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations()
112      */

113     @Override
114     protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
115         return Arrays.asList(Entity.class, MappedSuperclass.class);
116     }
117
118     /*
119      * (non-Javadoc)
120      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes()
121      */

122     @Override
123     protected Collection<Class<?>> getIdentifyingTypes() {
124         return Collections.<Class<?>> singleton(JpaRepository.class);
125     }
126
127     /*
128      * (non-Javadoc)
129      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
130      */

131     @Override
132     public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
133
134         Optional<String> transactionManagerRef = source.getAttribute("transactionManagerRef");
135         builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
136         builder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(source, source.getSource()));
137         builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
138         builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
139     }
140
141     /**
142      * XML configurations do not support {@link Character} values. This method catches the exception thrown and returns an
143      * {@link Optional#empty()} instead.
144      */

145     private static Optional<Character> getEscapeCharacter(RepositoryConfigurationSource source) {
146
147         try {
148             return source.getAttribute(ESCAPE_CHARACTER_PROPERTY, Character.class);
149         } catch (IllegalArgumentException ___) {
150             return Optional.empty();
151         }
152     }
153
154     /*
155      * (non-Javadoc)
156      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource)
157      */

158     @Override
159     public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
160
161         AnnotationAttributes attributes = config.getAttributes();
162
163         builder.addPropertyValue(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE,
164                 attributes.getBoolean(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE));
165     }
166
167     /*
168      * (non-Javadoc)
169      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.XmlRepositoryConfigurationSource)
170      */

171     @Override
172     public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {
173
174         Optional<String> enableDefaultTransactions = config.getAttribute(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE);
175
176         if (enableDefaultTransactions.isPresent() && StringUtils.hasText(enableDefaultTransactions.get())) {
177             builder.addPropertyValue(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE, enableDefaultTransactions.get());
178         }
179     }
180
181     /*
182      * (non-Javadoc)
183      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
184      */

185     @Override
186     public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource config) {
187
188         super.registerBeansForRoot(registry, config);
189
190         Object source = config.getSource();
191
192         registerLazyIfNotAlreadyRegistered(
193                 () -> new RootBeanDefinition(EntityManagerBeanDefinitionRegistrarPostProcessor.class), registry,
194                 EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME, source);
195
196         registerLazyIfNotAlreadyRegistered(() -> new RootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class),
197                 registry, JPA_MAPPING_CONTEXT_BEAN_NAME, source);
198
199         registerLazyIfNotAlreadyRegistered(() -> new RootBeanDefinition(PAB_POST_PROCESSOR), registry,
200                 AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME, source);
201
202         // Register bean definition for DefaultJpaContext
203
204         registerLazyIfNotAlreadyRegistered(() -> {
205
206             RootBeanDefinition contextDefinition = new RootBeanDefinition(DefaultJpaContext.class);
207             contextDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
208
209             return contextDefinition;
210
211         }, registry, JPA_CONTEXT_BEAN_NAME, source);
212
213         registerIfNotAlreadyRegistered(() -> new RootBeanDefinition(JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME), registry,
214                 JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME, source);
215
216         // EvaluationContextExtension for JPA specific SpEL functions
217
218         registerIfNotAlreadyRegistered(() -> {
219
220             Object value = AnnotationRepositoryConfigurationSource.class.isInstance(config) //
221                     ? config.getRequiredAttribute(ESCAPE_CHARACTER_PROPERTY, Character.class//
222                     : config.getAttribute(ESCAPE_CHARACTER_PROPERTY).orElse("\\");
223
224             BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JpaEvaluationContextExtension.class);
225             builder.addConstructorArgValue(value);
226
227             return builder.getBeanDefinition();
228
229         }, registry, JpaEvaluationContextExtension.class.getName(), source);
230     }
231
232     /*
233      * (non-Javadoc)
234      * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getConfigurationInspectionClassLoader(org.springframework.core.io.ResourceLoader)
235      */

236     @Override
237     protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) {
238
239         ClassLoader classLoader = loader.getClassLoader();
240
241         return classLoader != null && LazyJvmAgent.isActive(loader.getClassLoader())
242                 ? new InspectionClassLoader(loader.getClassLoader())
243                 : loader.getClassLoader();
244     }
245
246     /**
247      * Creates an anonymous factory to extract the actual {@link javax.persistence.EntityManager} from the
248      * {@link javax.persistence.EntityManagerFactory} bean name reference.
249      *
250      * @param config
251      * @param source
252      * @return
253      */

254     private static AbstractBeanDefinition getEntityManagerBeanDefinitionFor(RepositoryConfigurationSource config,
255             @Nullable Object source) {
256
257         BeanDefinitionBuilder builder = BeanDefinitionBuilder
258                 .rootBeanDefinition("org.springframework.orm.jpa.SharedEntityManagerCreator");
259         builder.setFactoryMethod("createSharedEntityManager");
260         builder.addConstructorArgReference(getEntityManagerBeanRef(config));
261
262         AbstractBeanDefinition bean = builder.getRawBeanDefinition();
263         bean.setSource(source);
264
265         return bean;
266     }
267
268     private static String getEntityManagerBeanRef(RepositoryConfigurationSource config) {
269
270         Optional<String> entityManagerFactoryRef = config.getAttribute("entityManagerFactoryRef");
271         return entityManagerFactoryRef.orElse("entityManagerFactory");
272     }
273
274     /**
275      * Utility to determine if a lazy Java agent is being used that might transform classes at a later time.
276      *
277      * @author Mark Paluch
278      * @since 2.1
279      */

280     @UtilityClass
281     static class LazyJvmAgent {
282
283         private static final Set<String> AGENT_CLASSES;
284
285         static {
286
287             Set<String> agentClasses = new LinkedHashSet<>();
288
289             agentClasses.add("org.springframework.instrument.InstrumentationSavingAgent");
290             agentClasses.add("org.eclipse.persistence.internal.jpa.deployment.JavaSECMPInitializerAgent");
291
292             AGENT_CLASSES = Collections.unmodifiableSet(agentClasses);
293         }
294
295         /**
296          * Determine if any agent is active.
297          *
298          * @return {@literal trueif an agent is active.
299          */

300         static boolean isActive(@Nullable ClassLoader classLoader) {
301
302             return AGENT_CLASSES.stream() //
303                     .anyMatch(agentClass -> ClassUtils.isPresent(agentClass, classLoader));
304         }
305     }
306 }
307