package org.springframework.data.jpa.repository.config;
import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*;
import lombok.experimental.UtilityClass;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.DefaultJpaContext;
import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor;
import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
private static final Class<?> PAB_POST_PROCESSOR = PersistenceAnnotationBeanPostProcessor.class;
private static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
private static final String ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE = "enableDefaultTransactions";
private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME = "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup";
private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter";
@Override
public String getModuleName() {
return "JPA";
}
@Override
public String getRepositoryFactoryBeanClassName() {
return JpaRepositoryFactoryBean.class.getName();
}
@Override
protected String getModulePrefix() {
return getModuleName().toLowerCase(Locale.US);
}
@Override
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Arrays.asList(Entity.class, MappedSuperclass.class);
}
@Override
protected Collection<Class<?>> getIdentifyingTypes() {
return Collections.<Class<?>> singleton(JpaRepository.class);
}
@Override
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
Optional<String> transactionManagerRef = source.getAttribute("transactionManagerRef");
builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
builder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(source, source.getSource()));
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
}
private static Optional<Character> getEscapeCharacter(RepositoryConfigurationSource source) {
try {
return source.getAttribute(ESCAPE_CHARACTER_PROPERTY, Character.class);
} catch (IllegalArgumentException ___) {
return Optional.empty();
}
}
@Override
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
AnnotationAttributes attributes = config.getAttributes();
builder.addPropertyValue(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE,
attributes.getBoolean(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE));
}
@Override
public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {
Optional<String> enableDefaultTransactions = config.getAttribute(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE);
if (enableDefaultTransactions.isPresent() && StringUtils.hasText(enableDefaultTransactions.get())) {
builder.addPropertyValue(ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE, enableDefaultTransactions.get());
}
}
@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource config) {
super.registerBeansForRoot(registry, config);
Object source = config.getSource();
registerLazyIfNotAlreadyRegistered(
() -> new RootBeanDefinition(EntityManagerBeanDefinitionRegistrarPostProcessor.class), registry,
EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME, source);
registerLazyIfNotAlreadyRegistered(() -> new RootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class),
registry, JPA_MAPPING_CONTEXT_BEAN_NAME, source);
registerLazyIfNotAlreadyRegistered(() -> new RootBeanDefinition(PAB_POST_PROCESSOR), registry,
AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME, source);
registerLazyIfNotAlreadyRegistered(() -> {
RootBeanDefinition contextDefinition = new RootBeanDefinition(DefaultJpaContext.class);
contextDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
return contextDefinition;
}, registry, JPA_CONTEXT_BEAN_NAME, source);
registerIfNotAlreadyRegistered(() -> new RootBeanDefinition(JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME), registry,
JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME, source);
registerIfNotAlreadyRegistered(() -> {
Object value = AnnotationRepositoryConfigurationSource.class.isInstance(config)
? config.getRequiredAttribute(ESCAPE_CHARACTER_PROPERTY, Character.class)
: config.getAttribute(ESCAPE_CHARACTER_PROPERTY).orElse("\\");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JpaEvaluationContextExtension.class);
builder.addConstructorArgValue(value);
return builder.getBeanDefinition();
}, registry, JpaEvaluationContextExtension.class.getName(), source);
}
@Override
protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) {
ClassLoader classLoader = loader.getClassLoader();
return classLoader != null && LazyJvmAgent.isActive(loader.getClassLoader())
? new InspectionClassLoader(loader.getClassLoader())
: loader.getClassLoader();
}
private static AbstractBeanDefinition getEntityManagerBeanDefinitionFor(RepositoryConfigurationSource config,
@Nullable Object source) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition("org.springframework.orm.jpa.SharedEntityManagerCreator");
builder.setFactoryMethod("createSharedEntityManager");
builder.addConstructorArgReference(getEntityManagerBeanRef(config));
AbstractBeanDefinition bean = builder.getRawBeanDefinition();
bean.setSource(source);
return bean;
}
private static String getEntityManagerBeanRef(RepositoryConfigurationSource config) {
Optional<String> entityManagerFactoryRef = config.getAttribute("entityManagerFactoryRef");
return entityManagerFactoryRef.orElse("entityManagerFactory");
}
@UtilityClass
static class LazyJvmAgent {
private static final Set<String> AGENT_CLASSES;
static {
Set<String> agentClasses = new LinkedHashSet<>();
agentClasses.add("org.springframework.instrument.InstrumentationSavingAgent");
agentClasses.add("org.eclipse.persistence.internal.jpa.deployment.JavaSECMPInitializerAgent");
AGENT_CLASSES = Collections.unmodifiableSet(agentClasses);
}
static boolean isActive(@Nullable ClassLoader classLoader) {
return AGENT_CLASSES.stream()
.anyMatch(agentClass -> ClassUtils.isPresent(agentClass, classLoader));
}
}
}