/*
* Copyright 2015 herd contributors
*
* 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 org.finra.herd.dao.config;
import java.util.Properties;
import javax.sql.DataSource;
import com.amazonaws.retry.RetryPolicy.BackoffStrategy;
import net.sf.ehcache.config.CacheConfiguration;
import org.apache.commons.configuration.ConfigurationConverter;
import org.apache.commons.configuration.DatabaseConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.finra.herd.core.ApplicationContextHolder;
import org.finra.herd.core.helper.ConfigurationHelper;
import org.finra.herd.dao.CacheKeyGenerator;
import org.finra.herd.dao.ReloadablePropertySource;
import org.finra.herd.dao.SimpleExponentialBackoffStrategy;
import org.finra.herd.model.dto.ConfigurationValue;
import org.finra.herd.model.jpa.ConfigurationEntity;
/**
* DAO Spring module configuration.
*/
@Configuration
// Component scan all packages, but exclude the configuration ones since they are explicitly specified.
@ComponentScan(value = "org.finra.herd.dao",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org\\.finra\\.herd\\.dao\\.config\\..*"))
@EnableTransactionManagement
@EnableCaching
public class DaoSpringModuleConfig implements CachingConfigurer
{
private static final Logger LOGGER = LoggerFactory.getLogger(DaoSpringModuleConfig.class);
@Autowired
private ConfigurationHelper configurationHelper;
/**
* The herd cache name.
*/
public static final String HERD_CACHE_NAME = "herd_cache";
/**
* The herd data source bean name.
*/
public static final String HERD_DATA_SOURCE_BEAN_NAME = "herdDataSource";
/**
* The herd transaction manager bean name.
*/
public static final String HERD_TRANSACTION_MANAGER_BEAN_NAME = "herdTransactionManager";
/**
* The Hibernate HBM2DDL Auto param bean name.
*/
public static final String HIBERNATE_HBM2DDL_AUTO_PARAM_BEAN_NAME = "hibernateHbm2DdlAutoParam";
/**
* Model packages to scan by entity manager.
*/
public static final String MODEL_PACKAGES_TO_SCAN = "org.finra.herd.model.jpa";
/**
* The transport client cache name.
*/
public static final String TRANSPORT_CLIENT_CACHE_NAME = "transport_client_cache";
/**
* The JPA entity manager factory.
*
* @return the entity manager factory.
*/
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory()
{
// Create the entity manager factory against our data source.
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(getHerdDataSource());
// Auto-scan our model classes for persistent objects.
entityManagerFactory.setPackagesToScan(MODEL_PACKAGES_TO_SCAN);
// Set the JPA vendor adapter using a configured Spring bean.
entityManagerFactory.setJpaVendorAdapter(getHibernateJpaVendorAdapter());
// Set JPA additional properties.
entityManagerFactory.setJpaProperties(jpaProperties());
return entityManagerFactory;
}
/**
* Gets the Hibernate JPA vendor adapter needed by the entity manager.
*
* @return the Hibernate JPA vendor adapter.
*/
private JpaVendorAdapter getHibernateJpaVendorAdapter()
{
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
// Set the database type.
String databaseType = configurationHelper.getProperty(ConfigurationValue.DATABASE_TYPE);
if (StringUtils.isBlank(databaseType))
{
throw new IllegalStateException(
String.format("No database type found. Ensure the \"%s\" configuration entry is configured.", ConfigurationValue.DATABASE_TYPE.getKey()));
}
Database database = Database.valueOf(databaseType);
LOGGER.info("jpaTargetDatabase={}", database);
hibernateJpaVendorAdapter.setDatabase(database);
hibernateJpaVendorAdapter.setGenerateDdl(false);
return hibernateJpaVendorAdapter;
}
/**
* Gets the JPA properties that contain our Hibernate specific configuration parameters.
*
* @return the JPA properties.
*/
private Properties jpaProperties()
{
// Create and return JPA properties.
Properties properties = new Properties();
// Set the Hibernate dialect.
String hibernateDialect = configurationHelper.getProperty(ConfigurationValue.HIBERNATE_DIALECT);
if (StringUtils.isBlank(hibernateDialect))
{
throw new IllegalStateException(String
.format("No hibernate dialect found. Ensure the \"%s\" configuration entry is configured.", ConfigurationValue.HIBERNATE_DIALECT.getKey()));
}
properties.setProperty(ConfigurationValue.HIBERNATE_DIALECT.getKey(), hibernateDialect);
LOGGER.info("hibernateDialect={}", properties.getProperty(ConfigurationValue.HIBERNATE_DIALECT.getKey()));
properties.setProperty("hibernate.query.substitutions", "true='Y', false='N', yes='Y', no='N'");
properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");
properties.setProperty("hibernate.cache.use_query_cache", "true");
properties.setProperty("hibernate.cache.use_second_level_cache", "true");
// Set the "show sql" flag.
properties.setProperty(ConfigurationValue.SHOW_SQL.getKey(), configurationHelper.getProperty(ConfigurationValue.SHOW_SQL));
LOGGER.info("hibernateShowSql={}", properties.getProperty(ConfigurationValue.SHOW_SQL.getKey()));
properties.setProperty("hibernate.archive.autodetection", "class, hbm");
// Set the Hibernate HBM2DDL Auto param if it is configured. This is only needed in JUnits.
String hibernateHbm2DdlAutoParam = getHibernateHbm2DdlAutoParam();
if (StringUtils.isNotBlank(hibernateHbm2DdlAutoParam))
{
properties.setProperty("hibernate.hbm2ddl.auto", hibernateHbm2DdlAutoParam);
}
return properties;
}
/**
* Our Spring JPA transaction manager that will manage the JPA transactions.
*
* @return the JPA transaction manager.
*/
@Bean
public JpaTransactionManager herdTransactionManager()
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(getHerdDataSource());
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
/**
* The database supplied property sources placeholder configurer that allows access to externalized properties from a database. This method also adds a new
* property source that contains the database properties to the environment.
*
* @return the property sources placeholder configurer.
*/
@Bean
public static PropertySourcesPlaceholderConfigurer databasePropertySourcesPlaceholderConfigurer()
{
// Get the configurable environment and add a new property source to it that contains the database properties.
// That way, the properties can be accessed via the environment or via an injected @Value annotation.
// We are adding this property source last so other property sources (e.g. system properties, environment variables) can be used
// to override the database properties.
Environment environment = ApplicationContextHolder.getApplicationContext().getEnvironment();
if (environment instanceof ConfigurableEnvironment)
{
ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
ReloadablePropertySource reloadablePropertySource =
new ReloadablePropertySource(ReloadablePropertySource.class.getName(), ConfigurationConverter.getProperties(getPropertyDatabaseConfiguration()),
getPropertyDatabaseConfiguration());
configurableEnvironment.getPropertySources().addLast(reloadablePropertySource);
}
return new PropertySourcesPlaceholderConfigurer();
}
/**
* Gets a database configuration that can be used to read database properties.
*
* @return the property database configuration.
*/
private static DatabaseConfiguration getPropertyDatabaseConfiguration()
{
return new DatabaseConfiguration(getHerdDataSource(), ConfigurationEntity.TABLE_NAME, ConfigurationEntity.COLUMN_KEY, ConfigurationEntity.COLUMN_VALUE);
}
/**
* Gets the data source bean from the application context statically. This is needed for static @Bean creation methods that won't have access to an
* auto-wired data source.
*
* @return the data source.
*/
public static DataSource getHerdDataSource()
{
return (DataSource) ApplicationContextHolder.getApplicationContext().getBean(HERD_DATA_SOURCE_BEAN_NAME);
}
/**
* Gets the Hibernate HBM2DDL bean from the application context statically.
*
* @return the Hibernate HBM2DDL auto param.
*/
public String getHibernateHbm2DdlAutoParam()
{
return (String) ApplicationContextHolder.getApplicationContext().getBean(HIBERNATE_HBM2DDL_AUTO_PARAM_BEAN_NAME);
}
/**
* Gets an EH Cache manager.
*
* @return the EH Cache manager.
*/
@Bean(destroyMethod = "shutdown")
public net.sf.ehcache.CacheManager ehCacheManager()
{
CacheConfiguration cacheConfiguration = new CacheConfiguration();
cacheConfiguration.setName(HERD_CACHE_NAME);
cacheConfiguration.setTimeToLiveSeconds(configurationHelper.getProperty(ConfigurationValue.HERD_CACHE_TIME_TO_LIVE_SECONDS, Long.class));
cacheConfiguration.setTimeToIdleSeconds(configurationHelper.getProperty(ConfigurationValue.HERD_CACHE_TIME_TO_IDLE_SECONDS, Long.class));
cacheConfiguration.setMaxElementsInMemory(configurationHelper.getProperty(ConfigurationValue.HERD_CACHE_MAX_ELEMENTS_IN_MEMORY, Integer.class));
cacheConfiguration.setMemoryStoreEvictionPolicy(configurationHelper.getProperty(ConfigurationValue.HERD_CACHE_MEMORY_STORE_EVICTION_POLICY));
// Adding a transport client cache to store the active transport client used to connect to the search index
CacheConfiguration transportClientCacheConfiguration = new CacheConfiguration();
transportClientCacheConfiguration.setName(TRANSPORT_CLIENT_CACHE_NAME);
// The maximum number of seconds an element can exist in the cache regardless of use.
// The element expires at this limit and will no longer be returned from the cache.
// The default value is 0, which means no timeToLive (TTL) eviction takes place (infinite lifetime).
transportClientCacheConfiguration
.setTimeToLiveSeconds(configurationHelper.getProperty(ConfigurationValue.TRANSPORT_CLIENT_CACHE_TIME_TO_LIVE_SECONDS, Long.class));
// The maximum number of seconds an element can exist in the cache without being accessed.
// The element expires at this limit and will no longer be returned from the cache.
// The default value is 0, which means no timeToIdle (TTI) eviction takes place (infinite lifetime).
transportClientCacheConfiguration
.setTimeToIdleSeconds(configurationHelper.getProperty(ConfigurationValue.TRANSPORT_CLIENT_CACHE_TIME_TO_IDLE_SECONDS, Long.class));
// The maximum entries to be held in the cache
transportClientCacheConfiguration
.setMaxElementsInMemory(configurationHelper.getProperty(ConfigurationValue.TRANSPORT_CLIENT_CACHE_MAX_ELEMENTS_IN_MEMORY, Integer.class));
// The policy used to evict elements from the MemoryStore. This can be one of:
// LRU - least recently used
// LFU - less frequently used
// FIFO - first in first out, the oldest element by creation time
// The default value is LRU
transportClientCacheConfiguration
.setMemoryStoreEvictionPolicy(configurationHelper.getProperty(ConfigurationValue.TRANSPORT_CLIENT_CACHE_MEMORY_STORE_EVICTION_POLICY));
net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
config.addCache(transportClientCacheConfiguration);
config.addCache(cacheConfiguration);
return net.sf.ehcache.CacheManager.create(config);
}
@Bean
@Override
public CacheManager cacheManager()
{
return new EhCacheCacheManager(ehCacheManager());
}
@Bean
@Override
public KeyGenerator keyGenerator()
{
return new CacheKeyGenerator();
}
/*
* No need to have a cache resolver, this is optional as we are defining cacheManager.
*/
@Bean
@Override
public CacheResolver cacheResolver()
{
return null;
}
@Bean
@Override
public CacheErrorHandler errorHandler()
{
return new SimpleCacheErrorHandler();
}
@Bean
public BackoffStrategy backoffStrategy()
{
return new SimpleExponentialBackoffStrategy();
}
/**
* Gets an LDAP context source.
*
* @return the LDAP context source
*/
@Bean
public LdapContextSource contextSource()
{
String ldapUrl = configurationHelper.getProperty(ConfigurationValue.LDAP_URL);
String ldapBase = configurationHelper.getProperty(ConfigurationValue.LDAP_BASE);
String ldapUserDn = configurationHelper.getProperty(ConfigurationValue.LDAP_USER_DN);
LOGGER.info("Creating LDAP context source using the following parameters: {}=\"{}\" {}=\"{}\" {}=\"{}\" ...", ConfigurationValue.LDAP_URL.getKey(),
ldapUrl, ConfigurationValue.LDAP_BASE.getKey(), ldapBase, ConfigurationValue.LDAP_USER_DN.getKey(), ldapUserDn);
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(ldapUrl);
contextSource.setBase(ldapBase);
contextSource.setUserDn(ldapUserDn);
contextSource.setPassword(configurationHelper.getProperty(ConfigurationValue.LDAP_PASSWORD));
return contextSource;
}
/**
* Gets an LDAP template.
*
* @return the LDAP template
*/
@Bean
public LdapTemplate ldapTemplate()
{
return new LdapTemplate(contextSource());
}
}