All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.holonplatform.jpa.spring.internal.JpaRegistrar Maven / Gradle / Ivy

There is a newer version: 5.7.0
Show newest version
/*
 * Copyright 2016-2017 Axioma srl.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.holonplatform.jpa.spring.internal;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManagerFactory;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import com.holonplatform.core.datastore.DatastoreConfigProperties;
import com.holonplatform.core.internal.Logger;
import com.holonplatform.datastore.jpa.internal.JpaDatastoreLogger;
import com.holonplatform.jdbc.DataSourceConfigProperties;
import com.holonplatform.jdbc.DatabasePlatform;
import com.holonplatform.jdbc.spring.EnableDataSource;
import com.holonplatform.jdbc.spring.internal.DataSourceRegistrar;
import com.holonplatform.jpa.spring.EnableJpa;
import com.holonplatform.jpa.spring.JpaConfigProperties;
import com.holonplatform.jpa.spring.JpaDatastoreConfigProperties;
import com.holonplatform.spring.EnvironmentConfigPropertyProvider;
import com.holonplatform.spring.PrimaryMode;
import com.holonplatform.spring.internal.AbstractConfigPropertyRegistrar;
import com.holonplatform.spring.internal.BeanRegistryUtils;
import com.holonplatform.spring.internal.DefaultEnvironmentConfigPropertyProvider;
import com.holonplatform.spring.internal.GenericDataContextBoundBeanDefinition;

/**
 * Registrar for JPA beans registration using {@link EnableJpa} annotation.
 * 
 * @since 5.0.0
 */
public class JpaRegistrar extends AbstractConfigPropertyRegistrar implements BeanClassLoaderAware, BeanFactoryAware {

	/*
	 * Logger
	 */
	private static final Logger logger = JpaDatastoreLogger.create();

	/**
	 * Beans class loader
	 */
	private ClassLoader beanClassLoader;

	/**
	 * Bean factory
	 */
	private BeanFactory beanFactory;

	/*
	 * (non-Javadoc)
	 * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
	 */
	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
	 */
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.
	 * core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {

		if (!annotationMetadata.isAnnotated(EnableJpa.class.getName())) {
			// ignore call from sub classes
			return;
		}

		Map attributes = annotationMetadata.getAnnotationAttributes(EnableJpa.class.getName());

		registerJpaBeans(registry, beanFactory, getEnvironment(), attributes, beanClassLoader);
	}

	/**
	 * Register JPA beans relying on given attributes
	 * @param registry BeanDefinitionRegistry
	 * @param beanFactory Bean factory
	 * @param environment Optional Environment
	 * @param attributes Attributes
	 * @param beanClassLoader Beans class loader
	 * @return Registered {@link EntityManagerFactory} bean name
	 */
	public static String registerJpaBeans(BeanDefinitionRegistry registry, BeanFactory beanFactory,
			Environment environment, Map attributes, ClassLoader beanClassLoader) {

		String dataContextId = BeanRegistryUtils.getAnnotationValue(attributes, "dataContextId", null);
		String dataSourceReference = BeanRegistryUtils.getAnnotationValue(attributes, "dataSourceReference", null);

		PrimaryMode primaryMode = BeanRegistryUtils.getAnnotationValue(attributes, "primary", PrimaryMode.AUTO);
		boolean primary = PrimaryMode.TRUE == primaryMode;

		String dsBeanName = dataSourceReference;
		if (dsBeanName == null) {
			// check DataSource
			dsBeanName = getDataSourceBeanName(registry, beanFactory, dataContextId);
			if (dsBeanName == null) {
				dsBeanName = BeanRegistryUtils.buildBeanName(dataContextId,
						EnableDataSource.DEFAULT_DATASOURCE_BEAN_NAME);
				// create and register a DataSource
				if (environment != null) {
					dsBeanName = DataSourceRegistrar.registerDataSource(environment, registry, dataContextId,
							primaryMode);
				}
			}
		}

		// check primary
		if (dsBeanName != null && registry.containsBeanDefinition(dsBeanName)) {
			if (!primary && PrimaryMode.AUTO == primaryMode) {
				BeanDefinition bd = registry.getBeanDefinition(dsBeanName);
				primary = bd.isPrimary();
			}
		}

		// check DataSource bean name
		if (dsBeanName == null) {
			throw new BeanCreationException("Failed to register JPA beans: missing DataSource bean name");
		}

		// JPA configuration

		JpaConfigProperties jpaConfigProperties = JpaConfigProperties.builder(dataContextId)
				.withPropertySource(EnvironmentConfigPropertyProvider.create(environment)).build();

		// Datastore configuration
		DatastoreConfigProperties datastoreConfig = null;
		try {
			datastoreConfig = DatastoreConfigProperties.builder(dataContextId)
					.withPropertySource(EnvironmentConfigPropertyProvider.create(environment)).build();
		} catch (Exception e) {
			logger.warn("Failed to load DatastoreConfigProperties", e);
		}

		// ------- EntityManagerFactory

		ValidationMode validationMode = BeanRegistryUtils.getAnnotationValue(attributes, "validationMode",
				ValidationMode.AUTO);
		SharedCacheMode sharedCacheMode = BeanRegistryUtils.getAnnotationValue(attributes, "sharedCacheMode",
				SharedCacheMode.UNSPECIFIED);

		String[] entityPackages = BeanRegistryUtils.getAnnotationValue(attributes, "entityPackages", new String[0]);
		Class[] entityPackageClasses = BeanRegistryUtils.getAnnotationValue(attributes, "entityPackageClasses",
				new Class[0]);

		List entityPackageNames = new LinkedList<>();
		entityPackageNames.addAll(Arrays.asList(entityPackages));
		for (Class cls : entityPackageClasses) {
			Package pkg = cls.getPackage();
			if (pkg != null && pkg.getName() != null) {
				entityPackageNames.add(pkg.getName());
			}
		}

		if (entityPackageNames.isEmpty()) {
			StringBuilder wrn = new StringBuilder();
			if (dataContextId != null) {
				wrn.append(" ");
			}
			wrn.append("No entity package names configured to build EntityManagerFactory");
			wrn.append(": expecting a persistence.xml file for unit name: ");
			wrn.append(dataContextId);
			logger.warn(wrn.toString());
		}

		EntityManagerFactoryConfigurator emfConfigurator = new EntityManagerFactoryConfigurator();
		emfConfigurator.setDataContextId(dataContextId);

		for (String entityPackage : entityPackageNames) {
			emfConfigurator.addEntityPackage(entityPackage);
		}

		emfConfigurator.setGenerateDdl(
				jpaConfigProperties.getConfigPropertyValue(JpaConfigProperties.GENERATE_DDL, Boolean.FALSE));

		emfConfigurator.setDialect(jpaConfigProperties.getConfigPropertyValue(JpaConfigProperties.DIALECT, null));

		// check datastore trace

		boolean showSql = false;
		if (datastoreConfig != null) {
			showSql = datastoreConfig.getConfigPropertyValue(DatastoreConfigProperties.TRACE, Boolean.FALSE);
		}
		if (!showSql) {
			showSql = jpaConfigProperties.getConfigPropertyValue(JpaConfigProperties.SHOW_SQL, Boolean.FALSE);
		}
		emfConfigurator.setShowSql(showSql);

		if (validationMode != null) {
			emfConfigurator.setValidationMode(validationMode);
		}
		if (sharedCacheMode != null) {
			emfConfigurator.setSharedCacheMode(sharedCacheMode);
		}

		emfConfigurator
				.setOrmPlatform(jpaConfigProperties.getConfigPropertyValue(JpaConfigProperties.ORM_PLATFORM, null));

		// Database platform
		DatabasePlatform database = jpaConfigProperties.getConfigPropertyValue(JpaConfigProperties.DATABASE, null);
		if (database != null) {
			emfConfigurator.setDatabase(database);
		} else {
			// check from datasource
			DataSourceConfigProperties dataSourceConfigProperties = DataSourceConfigProperties.builder(dataContextId)
					.withPropertySource(new DefaultEnvironmentConfigPropertyProvider(environment)).build();
			database = dataSourceConfigProperties.getConfigPropertyValue(DataSourceConfigProperties.PLATFORM, null);
			if (database != null) {
				emfConfigurator.setDatabase(database);
			}
		}

		// Vendor specific configuration properties
		Map vendorProperties = jpaConfigProperties.getSubPropertiesUsingPrefix("properties");
		for (Entry entry : vendorProperties.entrySet()) {
			emfConfigurator.addJpaProperty(entry.getKey(), entry.getValue());
		}

		GenericDataContextBoundBeanDefinition definition = new GenericDataContextBoundBeanDefinition();
		definition.setDataContextId(dataContextId);
		definition.setBeanClass(ConfigurableLocalContainerEntityManagerFactoryBean.class);
		definition.setAutowireCandidate(true);
		definition.setPrimary(primary);
		definition.setDependsOn(dsBeanName);

		if (dataContextId != null) {
			definition.addQualifier(new AutowireCandidateQualifier(Qualifier.class, dataContextId));
		}

		ConstructorArgumentValues avs = new ConstructorArgumentValues();
		avs.addIndexedArgumentValue(0, emfConfigurator);
		definition.setConstructorArgumentValues(avs);

		MutablePropertyValues pvs = new MutablePropertyValues();
		pvs.add("dataSource", new RuntimeBeanReference(dsBeanName));
		definition.setPropertyValues(pvs);

		String emfBeanName = BeanRegistryUtils.buildBeanName(dataContextId,
				EnableJpa.DEFAULT_ENTITYMANAGERFACTORY_BEAN_NAME);

		registry.registerBeanDefinition(emfBeanName, definition);

		StringBuilder log = new StringBuilder();
		if (dataContextId != null) {
			log.append(" ");
		}
		log.append("Registered EntityManagerFactory bean with name \"");
		log.append(emfBeanName);
		log.append("\"");
		if (dataContextId != null) {
			log.append(" and qualifier \"");
			log.append(dataContextId);
			log.append("\"");
		}
		log.append(" bound to DataSource bean: ");
		log.append(dsBeanName);
		logger.info(log.toString());

		// ------- TransactionManager

		TransactionManagerConfigurator tmConfigurator = new TransactionManagerConfigurator();
		tmConfigurator.setDataContextId(dataContextId);

		for (Entry entry : vendorProperties.entrySet()) {
			tmConfigurator.addJpaProperty(entry.getKey(), entry.getValue());
		}

		int transactionSynchronization = BeanRegistryUtils.getAnnotationValue(attributes, "transactionSynchronization",
				-1);
		if (transactionSynchronization > -1) {
			tmConfigurator.setTransactionSynchronization(transactionSynchronization);
		}

		int defaultTimeout = BeanRegistryUtils.getAnnotationValue(attributes, "defaultTimeout", -1);
		if (defaultTimeout > -1) {
			tmConfigurator.setDefaultTimeout(defaultTimeout);
		}

		boolean validateExistingTransaction = BeanRegistryUtils.getAnnotationValue(attributes,
				"validateExistingTransaction", false);
		if (validateExistingTransaction) {
			tmConfigurator.setValidateExistingTransaction(true);
		}

		boolean failEarlyOnGlobalRollbackOnly = BeanRegistryUtils.getAnnotationValue(attributes,
				"failEarlyOnGlobalRollbackOnly", false);
		if (failEarlyOnGlobalRollbackOnly) {
			tmConfigurator.setFailEarlyOnGlobalRollbackOnly(true);
		}

		boolean rollbackOnCommitFailure = BeanRegistryUtils.getAnnotationValue(attributes, "rollbackOnCommitFailure",
				false);
		if (rollbackOnCommitFailure) {
			tmConfigurator.setRollbackOnCommitFailure(true);
		}

		definition = new GenericDataContextBoundBeanDefinition();
		definition.setDataContextId(dataContextId);
		definition.setBeanClass(ConfigurableJpaTransactionManager.class);
		definition.setAutowireCandidate(true);
		definition.setPrimary(primary);
		definition.setDependsOn(emfBeanName);

		if (dataContextId != null) {
			definition.addQualifier(new AutowireCandidateQualifier(Qualifier.class, dataContextId));
		}

		avs = new ConstructorArgumentValues();
		avs.addIndexedArgumentValue(0, tmConfigurator);
		definition.setConstructorArgumentValues(avs);

		pvs = new MutablePropertyValues();
		pvs.add("entityManagerFactory", new RuntimeBeanReference(emfBeanName));
		definition.setPropertyValues(pvs);

		String tmBeanName = BeanRegistryUtils.buildBeanName(dataContextId,
				EnableDataSource.DEFAULT_TRANSACTIONMANAGER_BEAN_NAME);

		registry.registerBeanDefinition(tmBeanName, definition);

		log = new StringBuilder();
		if (dataContextId != null) {
			log.append(" ");
		}
		log.append("Registered JpaTransactionManager bean with name \"");
		log.append(tmBeanName);
		log.append("\"");
		if (dataContextId != null) {
			log.append(" and qualifier \"");
			log.append(dataContextId);
			log.append("\"");
		}
		logger.info(log.toString());

		// ------- Datastore

		boolean enableDatastore = BeanRegistryUtils.getAnnotationValue(attributes, "enableDatastore", true);
		if (enableDatastore) {
			// register a JpaDatastore

			JpaDatastoreConfigProperties defaultConfig = JpaDatastoreConfigProperties.builder(dataContextId)
					.withProperty(JpaDatastoreConfigProperties.PRIMARY,
							(primaryMode == PrimaryMode.TRUE) ? Boolean.TRUE : null)
					.withProperty(JpaDatastoreConfigProperties.AUTO_FLUSH,
							BeanRegistryUtils.getAnnotationValue(attributes, "autoFlush", false))
					.withProperty(JpaDatastoreConfigProperties.TRANSACTIONAL,
							BeanRegistryUtils.getAnnotationValue(attributes, "transactionalDatastore", true))

					.build();

			JpaDatastoreRegistrar.registerDatastore(registry, environment, dataContextId, emfBeanName, defaultConfig,
					beanClassLoader);
		}

		return emfBeanName;

	}

	/**
	 * Get the {@link DataSource} type bean name which corresponds to given data context id
	 * @param registry Bean registry
	 * @param beanFactory Bean factory
	 * @param dataContextId Optional data context id
	 * @return The DataSource bean name, or null if not found
	 */
	private static String getDataSourceBeanName(BeanDefinitionRegistry registry, BeanFactory beanFactory,
			String dataContextId) {
		// check unique DataSource if no data context id specified
		if (dataContextId == null && beanFactory instanceof ListableBeanFactory) {
			String[] dataSourceBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					(ListableBeanFactory) beanFactory, DataSource.class, false, false);
			if (dataSourceBeanNames != null && dataSourceBeanNames.length == 1) {
				return dataSourceBeanNames[0];
			}
		}
		// check bean name using data context id
		String dsBeanName = BeanRegistryUtils.buildBeanName(dataContextId,
				EnableDataSource.DEFAULT_DATASOURCE_BEAN_NAME);
		if (registry.containsBeanDefinition(dsBeanName) && beanFactory.isTypeMatch(dsBeanName, DataSource.class)) {
			return dsBeanName;
		}
		return null;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy