can JBpm use Spring beans instead of classes? - spring

We exploring migration of old J2EE project which has workflows kind of scenarios to be re-written with JBpm. I have referred few examples which mostly uses the Java class's for activity or task. Spring integration with JBPM is more of initialization of JBPM. Is it technically feasible to inject bean in substitution of java pojo classes?

It is possible to use spring bean in JBPM using domainSpecific work item handlers. Custom tasks can be defined with some required parameters such as bean name, method name, invocation parameters etc. These custom work item handler will implement ContextAware interface of Spring and get access to container.
This work in similar lines ServiceTask for JBPM which uses JAVA pojo classes.
<bean id="workflowLauncherProcessor" class="workflow.WorkflowLauncherProcessor">
<property name="manager" ref="runtimeManager"></property>
<property name="workItemHandlerMap">
<map>
<entry key="SpringAsyncTask" value-ref="asyncWorkItemHandler"></entry>
<entry key="SpringSyncTask" value-ref="syncWorkItemHandler"></entry>
<entry key="SpringDirectTask" value-ref="directWorkItemHandler"></entry>
</map>
</property>
</bean>
<bean id="directWorkItemHandler" class="workflow.handler.SpringDirectBeanInvokeWorkItemHandler">
</bean>
Implementation class will look as below
package workflow.handler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.management.InstanceNotFoundException;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jbpm.bpmn2.handler.WorkItemHandlerRuntimeException;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.api.runtime.process.WorkItemManager;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.scb.cashport.common.util.CommonConstants;
/**
* The Class SpringDirectBeanInvokeWorkItemHandler.
*
*/
public class SpringDirectBeanInvokeWorkItemHandler implements WorkItemHandler, ApplicationContextAware {
/** The Constant LOGGER. */
private static final Logger LOGGER = LogManager.getLogger(SpringDirectBeanInvokeWorkItemHandler.class);
/** The application context. */
private ApplicationContext applicationContext;
/*
* (non-Javadoc)
*
* #see
* org.kie.api.runtime.process.WorkItemHandler#executeWorkItem(org.kie.api
* .runtime.process.WorkItem, org.kie.api.runtime.process.WorkItemManager)
*/
#Override
public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
Map<String, Object> results = new HashMap<String, Object>();
try {
String bean = (String) workItem.getParameter(CommonConstants.BEAN);
Object inputParam = (Object) workItem.getParameter(CommonConstants.INPUT_PARAM);
boolean cascasdeInput = Boolean.valueOf(String.valueOf(workItem.getParameter(CommonConstants.CASCADE_INPUT)));
String operation = (String) workItem.getParameter(CommonConstants.OPERATION);
String parameterDefinition = (String) workItem.getParameter(CommonConstants.PARAMETER_TYPE);
String parameter = (String) workItem.getParameter(CommonConstants.PARAMETER);
LOGGER.info("Spring Direct Bean Work Item begin for bean: {} and input: {}", bean, inputParam);
Object outParam = null;
Object instance = applicationContext.getBean(bean);
if (instance != null) {
try {
Class<?>[] classes = null;
Object[] params = null;
if (parameterDefinition != null) {
if (parameterDefinition.contains(CommonConstants.COMMA)) {
String[] parameterDefinitionArray = parameterDefinition.split(CommonConstants.COMMA);
String[] parameternArray = parameter.split(CommonConstants.COMMA);
if (parameterDefinitionArray.length != parameternArray.length) {
throw new IllegalArgumentException("Paramter types and parameters are not matching");
}
classes = new Class<?>[parameterDefinitionArray.length];
params = new Object[parameterDefinitionArray.length];
for (int i = 0; i < parameterDefinitionArray.length; i++) {
classes[i] = Class.forName(parameterDefinitionArray[i]);
String paramKey = parameternArray[i];
if (paramKey.startsWith("'")) {
params[i] = paramKey.substring(1, (paramKey.length() - 1));
} else {
params[i] = BeanUtils.getProperty(inputParam, parameternArray[i]);
}
}
} else {
classes = new Class<?>[]{Class.forName(parameterDefinition)};
params = new Object[]{BeanUtils.getProperty(inputParam, parameter)};
}
}
Method method = instance.getClass().getMethod(operation, classes);
outParam = method.invoke(instance, params);
if (cascasdeInput) {
String resultParameter = (String) workItem.getParameter(CommonConstants.RESULT_PARAMETER);
BeanUtils.setProperty(inputParam, resultParameter, outParam);
results.put(CommonConstants.INPUT_PARAM, inputParam);
results.put(CommonConstants.OUT_PARAM, inputParam);
} else {
results.put(CommonConstants.OUT_PARAM, outParam);
}
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException cnfe) {
handleException(cnfe, instance.getClass().getName(), operation, parameterDefinition);
}
} else {
throw new InstanceNotFoundException(bean + " bean instance not found");
}
LOGGER.info("Spring Direct Bean Work Item completed for bean: {} and input: {}", bean, inputParam);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e.getCause());
throw new WorkItemHandlerRuntimeException(e);
} finally {
manager.completeWorkItem(workItem.getId(), results);
}
}
/*
* (non-Javadoc)
*
* #see
* org.kie.api.runtime.process.WorkItemHandler#abortWorkItem(org.kie.api
* .runtime.process.WorkItem, org.kie.api.runtime.process.WorkItemManager)
*/
#Override
public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.context.ApplicationContextAware#setApplicationContext
* (org.springframework.context.ApplicationContext)
*/
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Workflow launcher will be as below
RuntimeEngine engine = manager.getRuntimeEngine(EmptyContext.get());
KieSession ksession = engine.getKieSession();
for (Entry<String, WorkItemHandler> entry : workItemHandlerMap.entrySet()) {
ksession.getWorkItemManager().registerWorkItemHandler(entry.getKey(), entry.getValue());
}
Map<String, Object> parameter = new HashMap<String, Object>();
...
ProcessInstance processInstance = ksession.startProcess(processId, parameter);
Object variable = ((WorkflowProcessInstance) processInstance).getVariable("resultr");

Related

How to get method arguments using reflection

This method is first checked by beanDefintirions for the original Beans (Configureablelistablebeanfactory beanFactory is used; ).
ConfigurableListableBeanFactory beanFactory; injected.
Then all the methods of the original bean, which was obtained from the BeanFactory, are iterated over. After searching over the methods of a certain annotation, we get the Bean from the Applicationcontext.
This Bean is a proxy wrapper over the original Bean, which was formed at the - > postProcessBeforeInitialization() stage. Now through this bean, I call a method that has been marked with the annotation I need, but it requires another argument Obj ..args.
How do I get the missing argument ?
Использую Srping 5.x, java 11
private void runMethodWithPostProxyThirdPhaseAnnotation(String beanName, ApplicationContext applicationContext) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
try {
String originalBeanClassName = beanDefinition.getBeanClassName();
if (originalBeanClassName != null) {
Class<?> originalClass = Class.forName(originalBeanClassName);
Method[] methods = originalClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(PostProxyThirdPhase.class)) {
String originalMethodName = method.getName();
Class<?>[] parameterTypesFromOriginalMethod = method.getParameterTypes();
Object beanAfterProxy = applicationContext.getBean(beanName);
Method methodFromProxyBean = beanAfterProxy
.getClass()
.getMethod(originalMethodName, parameterTypesFromOriginalMethod);
methodFromProxyBean.invoke(beanAfterProxy, ?);
}
}
}
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
Solution
three-phase constructor (implementation).
The answer was in the documentation for the method :
public Object invoke(Object obj, Object... args)
#param args the arguments used for the method call
import bean.post.process.annotation.PostProxyThirdPhase;
import bean.post.process.bean.Quoter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
#Component
public class PostProxyInvokerContextListener implements ApplicationListener<ContextRefreshedEvent> {
private final ConfigurableListableBeanFactory beanFactory;
private final Quoter quoter;
public PostProxyInvokerContextListener(ConfigurableListableBeanFactory beanFactory, Quoter quoter) {
this.beanFactory = beanFactory;
this.quoter = quoter;
}
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
Arrays.stream(beanDefinitionNames)
.forEach(beanName -> runMethodWithPostProxyThirdPhaseAnnotation(beanName, applicationContext));
}
private void runMethodWithPostProxyThirdPhaseAnnotation(String beanName, ApplicationContext applicationContext) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
try {
String originalBeanClassName = beanDefinition.getBeanClassName();
if (originalBeanClassName != null) {
Class<?> originalClass = Class.forName(originalBeanClassName);
Method[] methods = originalClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(PostProxyThirdPhase.class)) {
String originalMethodName = method.getName();
Class<?>[] parameterTypesFromOriginalMethod = method.getParameterTypes();
Object beanAfterProxy = applicationContext.getBean(beanName);
Method methodFromProxyBean = beanAfterProxy
.getClass()
.getMethod(originalMethodName, parameterTypesFromOriginalMethod);
Object[] args = new Object[]{quoter};
methodFromProxyBean.invoke(beanAfterProxy, args);
}
}
}
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}

AWS lambda spring . Not able to load properties file

I have problem with loading the properties file where i reference the values from. Locally I am able to run it as expected. But AWS lambda function does not seem to work as expected since it is not able to laod properties file. Below is the handler written. I deploy MainHanlder.java function on lambda.
#Component
public class TestHandler implements RequestHandler<SNSEvent, Object> {
#Override
public String handleRequest(SNSEvent snsEvent, Context context) {
TestClient testClient = Application.getBean("pp",TestClient);
return null;
}
}
MainHandler.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import com.test.lambda.ApplicationConfiguration;
public class MainHandler extends SpringRequestHandler<SNSEvent, Object> {
/**
* Here we create the Spring {#link ApplicationContext} that will
* be used throughout our application.
*/
private static final ApplicationContext context =
new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
#Override
public ApplicationContext getApplicationContext() {
return context;
}
}
SpringRequestHandler.java
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.springframework.context.ApplicationContext;
#SuppressWarnings("unchecked")
public abstract class SpringRequestHandler<I, O> implements RequestHandler<I, O>, ApplicationContextProvider {
private final RequestHandler<I, O> handler;
public SpringRequestHandler() {
handler = getApplicationContext().getBean(RequestHandler.class);
}
#Override
public O handleRequest(final I input, final Context context) {
return (O) handler.handleRequest(input, context);
}
}
Application.java
public class Application {
private static final AnnotationConfigApplicationContext springContext = new AnnotationConfigApplicationContext();
private static boolean flag = Boolean.TRUE;
private static final XLogger logger = XLoggerFactory.getXLogger(Application.class);
public static <T> T getBean(String env, Class<T> clazz) {
InputStream i1 = null;
InputStream i2 = null;
if(flag) {
Properties rp = new Properties();
Properties ap = new Properties();
try {
System.out.println("print env " + env);
i1 = Application.class.getResourceAsStream(“/application-” + env + ".properties");
rp.load(i1);
i2 = Application.class.getResourceAsStream("/application-" + env + ".properties");
ap.load(i2);
PropertyPlaceholderConfigurer propertyOverrideConfigurer = new PropertyPlaceholderConfigurer();
propertyOverrideConfigurer.setPropertiesArray(new Properties[]{rp,ap});
springContext.scan(new String[]{"com.pinto.lambda"});
springContext.addBeanFactoryPostProcessor(propertyOverrideConfigurer);
try {
springContext.refresh();
}catch(IllegalStateException e) {
}
flag = Boolean.FALSE;
} catch (Exception e) {
logger.error("Exception in the Application - " +e.getMessage());
throw new RuntimeException("Unable to load properties " + e.getMessage());
}
return springContext.getBean(clazz);
}
ERROR TRACE - AWS CONSOLE LOGS
==================== FUNCTION OUTPUT ====================
{"errorMessage":"Unable to load properties null","errorType":"java.lang.RuntimeException","stackTrace":["com.pinto.lambda.Application.getBean(Application.java:65)","com.pinto.lambda.handler.GetWarehouseInventoryHandler.handleRequest(TestHandler.java:44)","com.pinto.lambda.handler.GetWarehouseInventoryHandler.handleRequest(TestHandler.java:1)","com.pinto.lambda.handler.SpringRequestHandler.handleRequest(SpringRequestHandler.java:19)"]}
==================== FUNCTION LOG OUTPUT ====================
print env pp
[ERROR] Exception in the Application - null
Unable to load properties null: java.lang.RuntimeException
java.lang.RuntimeException: Unable to load properties null
at com.pinto.lambda.Application.getBean(Application.java:65)
at com.pinto.lambda.handler.TestHandler.handleRequest(TestHandler.java:44)
at com.pinto.lambda.handler.TestHandler.handleRequest(TestHandler.java:1)
at com.pinto.lambda.handler.SpringRequestHandler.handleRequest(SpringRequestHandler.java:19)

Setting up a MultiTenantConnectionProvider using Hibernate 4.2 and Spring 3.1.1

I am currently trying to set up Hibernate for multi tenancy using the seperate Schema aproach.
After working on it for about 2 days now and browsing nearly every source I could find via Google I am starting to get quite frustrated.
Basicaly I am trying to follow the guide provided in the Hibernate devguide http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691
But unfortunately I am not able to find the ConnectionProviderUtils to build the ConnectionProvider.
Currently I am trying to figure out 2 Points:
Why the configure(Properties props) method of my MSSQLMultiTenantConnectionProvider is never called. From what I interpreted from the source of and description of different other ConnectionProvider implementions I am assuming this method is going to be called to initialize the ConnectionProvider.
Since I am not able to work with the configure(Properties props) I tried out other approaches of somehow obtaining the hibernate properties and DataSource specified in the application Context and the hibernate.cfg.xml. (Like injecting the datasource directly into the ConnectionProvider)
Any pointers to possible ways to solve this (Methods, Classes, Tutorials)
So here are the relevant parts of my implementation:
Data Source and Hibernate.cfg.xml:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url" value="jdbc:sqlserver://<host>:<port>;databaseName=<DbName>;" />
<property name="username" value=<username> />
<property name="password" value=<password> />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- property name="dataSource" ref="dataSource" /-->
<property name="annotatedClasses">
<list>
<value>c.h.utils.hibernate.User</value>
<value>c.h.utils.hibernate.Role</value>
<value>c.h.utils.hibernate.Tenant</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.SQLServerDialect
hibernate.show_sql=true
hibernate.multiTenancy=SCHEMA
hibernate.tenant_identifier_resolver=c.h.utils.hibernate.CurrentTenantIdentifierResolver
hibernate.multi_tenant_connection_provider=c.h.utils.hibernate.MSSQLMultiTenantConnectionProviderImpl
</value>
</property>
</bean>
MSSQLMultiTenantConnectionProviderImpl:
package c.hoell.utils.hibernate;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class MSSQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 8074002161278796379L;
#Autowired
private DataSource dataSource;
public void configure(Properties props) throws HibernateException {
}
#Override
public Connection getAnyConnection() throws SQLException {
Properties properties = getConnectionProperties(); //method which sets the hibernate properties
DriverManagerConnectionProviderImpl defaultProvider = new DriverManagerConnectionProviderImpl();
defaultProvider.configure(properties);
Connection con = defaultProvider.getConnection();
ResultSet rs = con.createStatement().executeQuery("SELECT * FROM [schema].table");
rs.close(); //the statement and sql is just to test the connection
return defaultProvider.getConnection();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
<--not sure how to implement this-->
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection){
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public boolean supportsAggressiveRelease() {
return false;
}
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MSSQLMultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
#SuppressWarnings("unchecked")
#Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Right now there are 2 possible approaches I see to obtaint the configurations i need from the config files. Either get the configure() method to run or somehow make the injection of the DataSource possible.
I guess the first one would be the better way.
An important thing to mention is that I had Hibernate up and running for only one tenant (means without using the MultiTenantConnectionProvider, using the standard ConnectionProvider used by Hibernate)
Already a big thanks to anyone who is reading this post. Looking forward to the answers.
Best regards
Update 1:
I have played around with this a bit and hardcoded the connectiondetails into my MultiTenantConnectionProvider (updated the Code above). This is working fine in regards to the MultiTenantConnectionProvider. But this is still not solving my problems.
Now my Application fails at initializing the Transaction Manager:
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
This is the top of the exception stacktrace:
Caused by: java.lang.NullPointerException at
org.springframework.orm.hibernate4.SessionFactoryUtils.getDataSource(SessionFactoryUtils.java:101)
at
org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:264)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
I traced this issue down in debug mode and found out that the problem is that my SessionFactory is somehow not getting hold of the DataSource. (It makes no difference whether I specify the DataSource in the hibernate.cfg.xml or not)
But when initializing the TransactionManager it tries to get the DataSource from the SessionFactory and fails with a NullPointerException as a result.
Does anyone have an hint at what point of the inner workings of hibernate this is failing? In all the documentation and posts I have seen there was no indication that I need to handle the injection of the DataSource into the SessionFactory.
For now I just guess I try to figure out how to get a DataSource into the needed place or how to change the initializing flow. If anyone has a better idea I would be really happy.
Edit: Also posted this in the Hibernate Forums now:
Update 2:
So I managed to get around this issue by setting the autodetectDataSource property in the TransactionManager to false:
<property name="autodetectDataSource" value="false"/>
I got this hint from the following post http://forum.springsource.org/showthread.php?123478-SessionFactory-configured-for-multi-tenancy-but-no-tenant-identifier-specified. Unfortunately I am now stuck at exactly that issue. ^^" But this is a problem for another topic. (Edit: Turns out this was only misconfiguration from earlier testing + one old dependency)
As for this topic the problem remains that I want to somehow be able to reuse the DataSource, which I already have in the configuration for the use of Spring Security anyway, for Hibernate to avoid the need for having to configure the DataSource in two places.
So the question still stands how to integrate the use of the DataSource in my MultiTenantConnectionProvider. Does anyone have an idea on where to find any hints on that?
According to Steve Ebersole's comments on the JIRA issue referred by one of this question's commenters (HHH-8752):
Well first, it is simply not true that Hibernate "instantiates the classes referred by ... MULTI_TENANT_CONNECTION_PROVIDER and MULTI_TENANT_IDENTIFIER_RESOLVER". Hibernate first tries to treat these settings as objects of their intended types, (MultiTenantConnectionProvider for MULTI_TENANT_CONNECTION_PROVIDER and CurrentTenantIdentifierResolver for MULTI_TENANT_IDENTIFIER_RESOLVER.
So just pass your beans in directly, configured however you want.
I just followed his suggestion and managed to make it work.
This is a CurrentTenantIdentifierResolver defined as a Spring Bean:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestURITenantIdentifierResolver implements CurrentTenantIdentifierResolver {
#Autowired
private HttpServletRequest request;
#Override
public String resolveCurrentTenantIdentifier() {
String[] pathElements = request.getRequestURI().split("/");
String tenant = pathElements[1];
return tenant;
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
This is a MultiTenantConnectionProvider defined as a Spring Bean:
#Component
public class SchemaPerTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
#Autowired
private DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
#Override
public void releaseAnyConnection(final Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(final String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute("USE " + tenantIdentifier);
} catch (SQLException e) {
throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e);
}
return connection;
}
#Override
public void releaseConnection(final String tenantIdentifier, final Connection connection) throws SQLException {
try {
connection.createStatement().execute("USE dummy");
} catch (SQLException e) {
// on error, throw an exception to make sure the connection is not returned to the pool.
// your requirements may differ
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" +
tenantIdentifier + "]",
e
);
} finally {
connection.close();
}
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
#Override
public boolean isUnwrappableAs(Class aClass) {
return false;
}
#Override
public <T> T unwrap(Class<T> aClass) {
return null;
}
}
And finally, this is a LocalContainerEntityManagerFactoryBean wired to make use of the two components above:
#Configuration
public class HibernateConfig {
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver tenantIdentifierResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPackagesToScan(VistoJobsApplication.class.getPackage().getName());
emfBean.setJpaVendorAdapter(jpaVendorAdapter());
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT,
MultiTenancyStrategy.SCHEMA);
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER,
multiTenantConnectionProvider);
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER,
tenantIdentifierResolver);
emfBean.setJpaPropertyMap(jpaProperties);
return emfBean;
}
}
The data source I'm using is made available automatically by Spring Boot.
I hope this helps.
Ok to wrap this up, here is what I ended up with the following. I use a simple CurrentTenantIdentifierResolver. And Instead of trying to inject the DataSource from somewhere else to my MultiTenantConnectionProviderImpl I create the DataSource (c3p0 ComboPooledDatasource) in the ConnectionProvider and started using only the connections provided by the my ConnectionProvider. So I eliminated the extra DataSource. To make the properties of the DataSource easily configurable I opted to get the configuration data from a properties file.
CurrentTenantIdentifierResolverImpl:
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
/**
* The method returns the RequestServerName as tenantidentifier.
* If no FacesContext is available null is returned.
*
* #return String tenantIdentifier
*/
#Override
public String resolveCurrentTenantIdentifier() {
if (FacesContext.getCurrentInstance() != null){
return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName();
} else {
return null;
}
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
MultiTenantConnectionProviderImpl:
Note that the PropertyUtil is just a simple local helper class to fetch my properties. Since it is nothing special I won't include it to not clutter the answer.
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 8074002161278796379L;
private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class );
private ComboPooledDataSource cpds;
private Properties properties;
/**
*
* Constructor. Initializes the ComboPooledDataSource based on the config.properties.
*
* #throws PropertyVetoException
*/
public MultiTenantConnectionProviderImpl() throws PropertyVetoException {
log.info("Initializing Connection Pool!");
properties = new Properties();
try {
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
cpds = new ComboPooledDataSource("Example");
cpds.setDriverClass(properties.getProperty("jdbc.driver"));
cpds.setJdbcUrl(properties.getProperty("jdbc.url"));
cpds.setUser(properties.getProperty("jdbc.user"));
cpds.setPassword(PropertyUtil.getCredential("jdbc.password"));
log.info("Connection Pool initialised!");
}
#Override
public Connection getAnyConnection() throws SQLException {
log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}",new int[]{cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){
log.error("Connection pool empty!");
}
return cpds.getConnection();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
log.debug("Get {} Connection:::Number of connections (max: busy - idle): {} : {} - {}",new Object[]{tenantIdentifier, cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){
log.error("Connection pool empty!");
}
return cpds.getConnection(tenantIdentifier, PropertyUtil.getCredential(tenantIdentifier));
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection){
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
#Override
public boolean supportsAggressiveRelease() {
return false;
}
#SuppressWarnings("rawtypes")
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
#SuppressWarnings("unchecked")
#Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
}
The c3p0 specific config is taken from the c3p0-config.xml:
<c3p0-config>
<named-config name="Example">
<property name="acquireIncrement">3</property>
<property name="preferredTestQuery">SELECT 1</property>
<property name="checkoutTimeout">2000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">1</property>
<property name="maxIdleTime">18000</property>
<property name="maxPoolSize">30</property>
<property name="minPoolSize">1</property>
<property name="maxStatements">50</property>
<property name="testConnectionOnCheckin">true</property>
</named-config>
</c3p0-config>
And the db specific properties are provided by a config.properties file:
jdbc.url=<serverUrl>
jdbc.driver=<driverClass>
jdbc.dbName=<dBname>
jdbc.dbowner=<dbo>
jdbc.username=<user>
jdbc.password=<password>
hibernate.dialect=<hibernateDialect>
hibernate.debug=false
The credentials are fetched in a similar fashion from another file.
Any feedback providing improvements is appreciated.
The suggestion of using <map> instead of <props> seem to work for me. https://jira.springsource.org/browse/SPR-10823#comment-94855
<bean id="multiTenantConnectionProvider"
class="test.MultiTenantConnectionProviderImpl"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="packagesToScan" value="test.models" />
<property name="hibernateProperties">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL82Dialect"/>
<entry key="hibernate.multiTenancy" value="SCHEMA"/>
<entry key="hibernate.tenant_identifier_resolver" value="test.CurrentTenantIdentifierResolverImpl"/>
<entry key="hibernate.multi_tenant_connection_provider" value-ref="multiTenantConnectionProvider"/>
</map>
</property>
</bean>
As of Spring Framework version 3.2.4 there is no way to have the MultiTenantConnectionProvider and CurrentTenantIdentifierResolver managed by the Spring container. This creates many obstacles such as using an already configured DataSource, WebContext and other Spring managed beans and features. I have tried to find a cleaner solutions but came up with only one:
Extend org.springframework.orm.hibernate4.LocalSessionFactoryBuilder and write a custom LocalSessionFactoryBean (can't subclass and provide a LocalSessionFactoryBuilder, its basically a copy of the original with a tiny change)
Here goes:
package com.levitech.hibernate;
import javax.sql.DataSource;
import org.hibernate.cfg.Environment;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.core.io.ResourceLoader;
public class CustomLocalSessionFactoryBuilder extends org.springframework.orm.hibernate4.LocalSessionFactoryBuilder {
public CustomLocalSessionFactoryBuilder(DataSource dataSource,ResourceLoader resourceLoader, MultiTenantConnectionProvider connectionProvider,
CurrentTenantIdentifierResolver tenantIdResolver) {
super(dataSource, resourceLoader);
getProperties().put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
getProperties().put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdResolver);
}
}
The LocalSessionFactoryBean replacement (The only change is in the afterPropertiesSet() method to use the custom LocalSessionFactoryBuilder):
package com.levitech.hibernate;
/*
* Copyright 2002-2013 the original author or authors.
*
* 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.
*/
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.NamingStrategy;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder;
/**
* {#link org.springframework.beans.factory.FactoryBean} that creates a Hibernate
* {#link org.hibernate.SessionFactory}. This is the usual way to set up a shared
* Hibernate SessionFactory in a Spring application context; the SessionFactory can
* then be passed to Hibernate-based data access objects via dependency injection.
*
* <p><b>NOTE:</b> This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher.
* It is similar in role to the same-named class in the {#code orm.hibernate3} package.
* However, in practice, it is closer to {#code AnnotationSessionFactoryBean} since
* its core purpose is to bootstrap a {#code SessionFactory} from annotation scanning.
*
* <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make
* sure to either specify the {#link #setJtaTransactionManager "jtaTransactionManager"}
* bean property or to set the "hibernate.transaction.factory_class" property to
* {#link org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory}.
* Otherwise, Hibernate's smart flushing mechanism won't work properly.
*
* #author Juergen Hoeller
* #since 3.1
* #see #setDataSource
* #see #setPackagesToScan
* #see LocalSessionFactoryBuilder
*/
public class CustomLocalSessionFactoryBean extends HibernateExceptionTranslator
implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
private MultiTenantConnectionProvider multiTenantConnectionProvider;
private CurrentTenantIdentifierResolver tenantIdResolver;
private DataSource dataSource;
private Resource[] configLocations;
private String[] mappingResources;
private Resource[] mappingLocations;
private Resource[] cacheableMappingLocations;
private Resource[] mappingJarLocations;
private Resource[] mappingDirectoryLocations;
private Interceptor entityInterceptor;
private NamingStrategy namingStrategy;
private Properties hibernateProperties;
private Class<?>[] annotatedClasses;
private String[] annotatedPackages;
private String[] packagesToScan;
private Object jtaTransactionManager;
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private Configuration configuration;
private SessionFactory sessionFactory;
public MultiTenantConnectionProvider getMultiTenantConnectionProvider() {
return multiTenantConnectionProvider;
}
public void setMultiTenantConnectionProvider(
MultiTenantConnectionProvider multiTenantConnectionProvider) {
this.multiTenantConnectionProvider = multiTenantConnectionProvider;
}
public CurrentTenantIdentifierResolver getTenantIdResolver() {
return tenantIdResolver;
}
public void setTenantIdResolver(CurrentTenantIdentifierResolver tenantIdResolver) {
this.tenantIdResolver = tenantIdResolver;
}
/**
* Set the DataSource to be used by the SessionFactory.
* If set, this will override corresponding settings in Hibernate properties.
* <p>If this is set, the Hibernate settings should not define
* a connection provider to avoid meaningless double configuration.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Set the location of a single Hibernate XML config file, for example as
* classpath resource "classpath:hibernate.cfg.xml".
* <p>Note: Can be omitted when all necessary properties and mapping
* resources are specified locally via this bean.
* #see org.hibernate.cfg.Configuration#configure(java.net.URL)
*/
public void setConfigLocation(Resource configLocation) {
this.configLocations = new Resource[] {configLocation};
}
/**
* Set the locations of multiple Hibernate XML config files, for example as
* classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
* <p>Note: Can be omitted when all necessary properties and mapping
* resources are specified locally via this bean.
* #see org.hibernate.cfg.Configuration#configure(java.net.URL)
*/
public void setConfigLocations(Resource[] configLocations) {
this.configLocations = configLocations;
}
/**
* Set Hibernate mapping resources to be found in the class path,
* like "example.hbm.xml" or "mypackage/example.hbm.xml".
* Analogous to mapping entries in a Hibernate XML config file.
* Alternative to the more generic setMappingLocations method.
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* #see #setMappingLocations
* #see org.hibernate.cfg.Configuration#addResource
*/
public void setMappingResources(String[] mappingResources) {
this.mappingResources = mappingResources;
}
/**
* Set locations of Hibernate mapping files, for example as classpath
* resource "classpath:example.hbm.xml". Supports any resource location
* via Spring's resource abstraction, for example relative paths like
* "WEB-INF/mappings/example.hbm.xml" when running in an application context.
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* #see org.hibernate.cfg.Configuration#addInputStream
*/
public void setMappingLocations(Resource[] mappingLocations) {
this.mappingLocations = mappingLocations;
}
/**
* Set locations of cacheable Hibernate mapping files, for example as web app
* resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
* via Spring's resource abstraction, as long as the resource can be resolved
* in the file system.
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* #see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File)
*/
public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) {
this.cacheableMappingLocations = cacheableMappingLocations;
}
/**
* Set locations of jar files that contain Hibernate mapping resources,
* like "WEB-INF/lib/example.hbm.jar".
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* #see org.hibernate.cfg.Configuration#addJar(java.io.File)
*/
public void setMappingJarLocations(Resource[] mappingJarLocations) {
this.mappingJarLocations = mappingJarLocations;
}
/**
* Set locations of directories that contain Hibernate mapping resources,
* like "WEB-INF/mappings".
* <p>Can be used to add to mappings from a Hibernate XML config file,
* or to specify all mappings locally.
* #see org.hibernate.cfg.Configuration#addDirectory(java.io.File)
*/
public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) {
this.mappingDirectoryLocations = mappingDirectoryLocations;
}
/**
* Set a Hibernate entity interceptor that allows to inspect and change
* property values before writing to and reading from the database.
* Will get applied to any new Session created by this factory.
* #see org.hibernate.cfg.Configuration#setInterceptor
*/
public void setEntityInterceptor(Interceptor entityInterceptor) {
this.entityInterceptor = entityInterceptor;
}
/**
* Set a Hibernate NamingStrategy for the SessionFactory, determining the
* physical column and table names given the info in the mapping document.
* #see org.hibernate.cfg.Configuration#setNamingStrategy
*/
public void setNamingStrategy(NamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
/**
* Set Hibernate properties, such as "hibernate.dialect".
* <p>Note: Do not specify a transaction provider here when using
* Spring-driven transactions. It is also advisable to omit connection
* provider settings and use a Spring-set DataSource instead.
* #see #setDataSource
*/
public void setHibernateProperties(Properties hibernateProperties) {
this.hibernateProperties = hibernateProperties;
}
/**
* Return the Hibernate properties, if any. Mainly available for
* configuration through property paths that specify individual keys.
*/
public Properties getHibernateProperties() {
if (this.hibernateProperties == null) {
this.hibernateProperties = new Properties();
}
return this.hibernateProperties;
}
/**
* Specify annotated entity classes to register with this Hibernate SessionFactory.
* #see org.hibernate.cfg.Configuration#addAnnotatedClass(Class)
*/
public void setAnnotatedClasses(Class<?>[] annotatedClasses) {
this.annotatedClasses = annotatedClasses;
}
/**
* Specify the names of annotated packages, for which package-level
* annotation metadata will be read.
* #see org.hibernate.cfg.Configuration#addPackage(String)
*/
public void setAnnotatedPackages(String[] annotatedPackages) {
this.annotatedPackages = annotatedPackages;
}
/**
* Specify packages to search for autodetection of your entity classes in the
* classpath. This is analogous to Spring's component-scan feature
* ({#link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
*/
public void setPackagesToScan(String... packagesToScan) {
this.packagesToScan = packagesToScan;
}
/**
* Set the Spring {#link org.springframework.transaction.jta.JtaTransactionManager}
* or the JTA {#link javax.transaction.TransactionManager} to be used with Hibernate,
* if any.
* #see LocalSessionFactoryBuilder#setJtaTransactionManager
*/
public void setJtaTransactionManager(Object jtaTransactionManager) {
this.jtaTransactionManager = jtaTransactionManager;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
}
public void afterPropertiesSet() throws IOException {
LocalSessionFactoryBuilder sfb = new CustomLocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver, multiTenantConnectionProvider, tenantIdResolver);
if (this.configLocations != null) {
for (Resource resource : this.configLocations) {
// Load Hibernate configuration from given location.
sfb.configure(resource.getURL());
}
}
if (this.mappingResources != null) {
// Register given Hibernate mapping definitions, contained in resource files.
for (String mapping : this.mappingResources) {
Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader());
sfb.addInputStream(mr.getInputStream());
}
}
if (this.mappingLocations != null) {
// Register given Hibernate mapping definitions, contained in resource files.
for (Resource resource : this.mappingLocations) {
sfb.addInputStream(resource.getInputStream());
}
}
if (this.cacheableMappingLocations != null) {
// Register given cacheable Hibernate mapping definitions, read from the file system.
for (Resource resource : this.cacheableMappingLocations) {
sfb.addCacheableFile(resource.getFile());
}
}
if (this.mappingJarLocations != null) {
// Register given Hibernate mapping definitions, contained in jar files.
for (Resource resource : this.mappingJarLocations) {
sfb.addJar(resource.getFile());
}
}
if (this.mappingDirectoryLocations != null) {
// Register all Hibernate mapping definitions in the given directories.
for (Resource resource : this.mappingDirectoryLocations) {
File file = resource.getFile();
if (!file.isDirectory()) {
throw new IllegalArgumentException(
"Mapping directory location [" + resource + "] does not denote a directory");
}
sfb.addDirectory(file);
}
}
if (this.entityInterceptor != null) {
sfb.setInterceptor(this.entityInterceptor);
}
if (this.namingStrategy != null) {
sfb.setNamingStrategy(this.namingStrategy);
}
if (this.hibernateProperties != null) {
sfb.addProperties(this.hibernateProperties);
}
if (this.annotatedClasses != null) {
sfb.addAnnotatedClasses(this.annotatedClasses);
}
if (this.annotatedPackages != null) {
sfb.addPackages(this.annotatedPackages);
}
if (this.packagesToScan != null) {
sfb.scanPackages(this.packagesToScan);
}
if (this.jtaTransactionManager != null) {
sfb.setJtaTransactionManager(this.jtaTransactionManager);
}
// Build SessionFactory instance.
this.configuration = sfb;
this.sessionFactory = buildSessionFactory(sfb);
}
/**
* Subclasses can override this method to perform custom initialization
* of the SessionFactory instance, creating it via the given Configuration
* object that got prepared by this LocalSessionFactoryBean.
* <p>The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory.
* A custom implementation could prepare the instance in a specific way (e.g. applying
* a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
* #param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
* #return the SessionFactory instance
* #see LocalSessionFactoryBuilder#buildSessionFactory
*/
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
return sfb.buildSessionFactory();
}
/**
* Return the Hibernate Configuration object used to build the SessionFactory.
* Allows for access to configuration metadata stored there (rarely needed).
* #throws IllegalStateException if the Configuration object has not been initialized yet
*/
public final Configuration getConfiguration() {
if (this.configuration == null) {
throw new IllegalStateException("Configuration not initialized yet");
}
return this.configuration;
}
public SessionFactory getObject() {
return this.sessionFactory;
}
public Class<?> getObjectType() {
return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
}
public boolean isSingleton() {
return true;
}
public void destroy() {
this.sessionFactory.close();
}
}
In your application context define the beans:
<bean id="multiTenantProvider" class="com.levitech.hibernate.MultiTenantConnectionProviderImpl" depends-on="myDataSource" lazy-init="false"></bean>
<bean id="tenantIdResolver" class="com.levitech.hibernate.TenantIdResolver"></bean>
<bean id="sessionFactory" class="com.levitech.hibernate.CustomLocalSessionFactoryBean" depends-on="liquibase, myDataSource, multiTenantProvider">
<property name="dataSource" ref="myDataSource"></property>
<property name="multiTenantConnectionProvider" ref="multiTenantProvider"></property>
<property name="tenantIdResolver" ref="tenantIdResolver"></property>
<property name="mappingLocations" value="classpath*:hibernate/**/*.hbm.xml" />
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.cache.use_query_cache=true
hibernate.cache.use_second_level_cache=true
hibernate.multiTenancy=SCHEMA
</value>
</property>
</bean>
Do NOT provide values for the following in your hibernate properties:
hibernate.tenant_identifier_resolver and
hibernate.multi_tenant_connection_provider
You are all set and all your beans are Spring managed. You are free to use DI again! Hope this helps someone. I did put in a Jira request for the feature..
Using these guys' responses and this link, I put this together without Spring or anything else but C3P0.
I had to add these 2 properties to my hibernate config
properties.setProperty("hibernate.multiTenancy", "SCHEMA");
properties.setProperty("hibernate.multi_tenant_connection_provider", MultiTenantConnectionProviderImpl.class.getName());
HibernateUtils.java
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* #author Alex
*/
public class HibernateUtils {
private static final Logger logger = LoggerFactory.getLogger(HibernateUtils.class);
private static SessionFactory sessionFactory;
static{
init();
}
public static void init(){
try {
Configuration configuration = new Configuration()
.setProperties(ConnectionPropertiesUtils.getProperties());
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public static Session getTenantSession(String tenant){
return getSession(tenant);
}
public static Session getAuthSession(){
return getSession("AUTH");
}
public static Session getLogSession(){
return getSession("LOG");
}
public static Session getConfigSession(){
return getSession("CONFIG");
}
public static Session getSession(String tenant)
throws HibernateException {
if(sessionFactory == null){
init();
}
return sessionFactory.withOptions().tenantIdentifier(tenant).openSession();
}
#Deprecated
public static Session getSession()
throws HibernateException {
if(sessionFactory == null){
init();
}
return sessionFactory.openSession();
}
}
And MultiTenantConnectionProviderImpl.java
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.Stoppable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simplistic implementation for illustration purposes showing a single
* connection pool used to serve multiple schemas using "connection altering".
* Here we use the T-SQL specific USE command; Oracle users might use the ALTER
* SESSION SET SCHEMA command; etc.
*/
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable {
private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class);
private ComboPooledDataSource cpds;
public MultiTenantConnectionProviderImpl() throws PropertyVetoException {
log.info("Initializing Connection Pool!");
cpds = new ComboPooledDataSource("Example");
cpds.setDriverClass(ConnectionPropertiesUtils.getProperty("hibernate.connection.driver_class"));
cpds.setJdbcUrl(ConnectionPropertiesUtils.getProperty("hibernate.connection.url"));
cpds.setUser(ConnectionPropertiesUtils.getProperty("hibernate.connection.username"));
cpds.setPassword(ConnectionPropertiesUtils.getProperty("hibernate.connection.password"));
log.info("Connection Pool initialised!");
}
#Override
public Connection getAnyConnection() throws SQLException {
log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}", new int[]{cpds.getMaxPoolSize(), cpds.getNumBusyConnectionsAllUsers(), cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()) {
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers() == 0) {
log.error("Connection pool empty!");
}
return cpds.getConnection();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
//This is DB specific syntax. This work for MSSQL and MySQL
//Oracle uses the ALTER SESSION SET SCHEMA command
connection.createStatement().execute("USE " + tenantIdentifier);
} catch (SQLException e) {
throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
}
return connection;
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) {
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
#Override
public boolean supportsAggressiveRelease() {
return false;
}
#SuppressWarnings("rawtypes")
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProviderImpl.class.isAssignableFrom(unwrapType);
}
#SuppressWarnings("unchecked")
#Override
public <T> T unwrap(Class<T> unwrapType) {
if (isUnwrappableAs(unwrapType)) {
return (T) this;
} else {
throw new UnknownUnwrapTypeException(unwrapType);
}
}
public void stop() {
cpds.close();
}
}

JSF View scope in Spring

Is there any scope like JSF #ViewScoped in Spring 3.0? I have an application using JSF+Spring where backing beans are managed by Spring. I didn't find any scope like JSF wiew scope in Spring. I saw the blog Porting JSF 2.0’s ViewScope to Spring 3.0, but it didn't work for me.
Here's my attempt on the custom Spring scope:
import java.util.Map;
import javax.faces.context.FacesContext;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
/**
* Implements the JSF View Scope for use by Spring. This class is registered as a Spring bean with the CustomScopeConfigurer.
*/
public class ViewScope implements Scope {
public Object get(String name, ObjectFactory<?> objectFactory) {
System.out.println("**************************************************");
System.out.println("-------------------- Getting objects For View Scope ----------");
System.out.println("**************************************************");
if (FacesContext.getCurrentInstance().getViewRoot() != null) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
} else {
return null;
}
}
public Object remove(String name) {
System.out.println("**************************************************");
System.out.println("-------------------- View Scope object Removed ----------");
System.out.println("**************************************************");
if (FacesContext.getCurrentInstance().getViewRoot() != null) {
return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
} else {
return null;
}
}
public void registerDestructionCallback(String name, Runnable callback) {
// Do nothing
}
public Object resolveContextualObject(String key) { return null;
}
public String getConversationId() {
return null;
}
}
application-context.xml:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="view">
<bean class="com.delta.beans.ViewScope"/>
</entry>
</map>
</property>
</bean>
Recently I've created maven artifact which will solve this problem.
See my github javaplugs/spring-jsf repository.
I did something like this without Porting bean to Spring. It's working for me.
#ManagedBean(name="bean")
#ViewScoped // actual jsf viewscoped only with javax.faces.viewscoped import
public class Bean implements
Serializable {
#ManagedProperty(value="#{appService}") // Spring Manged Bean and singleton
private transient AppService appService;
// Getting AppService Object which is singleton in the application during deserialization
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
FacesContext context = FacesContext.getCurrentInstance();
appService = (AppService)context.getApplication()
.evaluateExpressionGet(context, "#{appService}", AppService.class);
}
}
public class ViewScopeCallbackRegistrer implements ViewMapListener {
#SuppressWarnings("unchecked")
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
if (event instanceof PostConstructViewMapEvent) {
PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent) event;
UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
viewRoot.getViewMap().put(
ViewScope.VIEW_SCOPE_CALLBACKS,
new HashMap<String, Runnable>()
);
} else if (event instanceof PreDestroyViewMapEvent) {
PreDestroyViewMapEvent viewMapEvent = (PreDestroyViewMapEvent) event;
UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
Map<String, Runnable> callbacks = (Map<String, Runnable>) viewRoot
.getViewMap().get(ViewScope.VIEW_SCOPE_CALLBACKS);
if (callbacks != null) {
for (Runnable c : callbacks.values()) {
c.run();
}
callbacks.clear();
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
}
public class ViewScope implements Scope {
public static final String VIEW_SCOPE_CALLBACKS = "viewScope.callbacks";
#Override
public synchronized Object get(String name, ObjectFactory<?> objectFactory) {
Object instance = this.getViewMap().get(name);
if(instance == null){
instance = objectFactory.getObject();
this.getViewMap().put(name, instance);
}
return instance;
}
#SuppressWarnings("unchecked")
#Override
public Object remove(String name) {
Object instance = this.getViewMap().remove(name);
if(instance == null){
Map<String, Runnable> callbacks = (Map<String, Runnable>) this.getViewMap().get(VIEW_SCOPE_CALLBACKS);
if(callbacks != null)
callbacks.remove(name);
}
return instance;
}
/**
* Responsável por registrar uma chamada de destruição ao bean
* que será armazenadano [b]viewMap[/b] da [b]ViewRoot[/b](nossa página que será mostrada)
* #see #getViewMap()
* #param name - nome do bean
* #param runnable
*/
#SuppressWarnings("unchecked")
#Override
public void registerDestructionCallback(String name, Runnable runnable) {
Map<String, Runnable> callbacks = (Map<String, Runnable>) this.getViewMap().get(VIEW_SCOPE_CALLBACKS);
if(callbacks != null)
callbacks.put(name, runnable);
}
#Override
public Object resolveContextualObject(String key) {
FacesContext facesContext = FacesContext.getCurrentInstance();
FacesRequestAttributes facesResquestAttributes = new FacesRequestAttributes(facesContext);
return facesResquestAttributes.resolveReference(key);
}
#Override
public String getConversationId() {
FacesContext facesContext = FacesContext.getCurrentInstance();
FacesRequestAttributes facesResquestAttributes = new FacesRequestAttributes(facesContext);
return facesResquestAttributes.getSessionId() + "-" + facesContext.getViewRoot().getViewId();
}
private Map<String, Object> getViewMap(){
return FacesContext.getCurrentInstance().getViewRoot().getViewMap();
}
}
I have tried a work around for the Jsf view bean memory leak issue for both Jsf 2.1 & Jsf 2.2. Try the code in following link Memory leak with ViewScoped bean?. It will clear the view bean in session while navigating to next page.

Access properties file programmatically with Spring?

We use the code below to inject Spring beans with properties from a properties file.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:/my.properties"/>
</bean>
<bean id="blah" class="abc">
<property name="path" value="${the.path}"/>
</bean>
Is there a way we can access the properties programmatically? I'm trying to do some code without dependency injection. So I'd like to just have some code like this:
PropertyPlaceholderConfigurer props = new PropertyPlaceholderConfigurer();
props.load("classpath:/my.properties");
props.get("path");
How about PropertiesLoaderUtils?
Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
If all you want to do is access placeholder value from code, there is the #Value annotation:
#Value("${settings.some.property}")
String someValue;
To access placeholders From SPEL use this syntax:
#('${settings.some.property}')
To expose configuration to views that have SPEL turned off, one can use this trick:
package com.my.app;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.stereotype.Component;
#Component
public class PropertyPlaceholderExposer implements Map<String, String>, BeanFactoryAware {
ConfigurableBeanFactory beanFactory;
#Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
protected String resolveProperty(String name) {
String rv = beanFactory.resolveEmbeddedValue("${" + name + "}");
return rv;
}
#Override
public String get(Object key) {
return resolveProperty(key.toString());
}
#Override
public boolean containsKey(Object key) {
try {
resolveProperty(key.toString());
return true;
}
catch(Exception e) {
return false;
}
}
#Override public boolean isEmpty() { return false; }
#Override public Set<String> keySet() { throw new UnsupportedOperationException(); }
#Override public Set<java.util.Map.Entry<String, String>> entrySet() { throw new UnsupportedOperationException(); }
#Override public Collection<String> values() { throw new UnsupportedOperationException(); }
#Override public int size() { throw new UnsupportedOperationException(); }
#Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); }
#Override public void clear() { throw new UnsupportedOperationException(); }
#Override public String put(String key, String value) { throw new UnsupportedOperationException(); }
#Override public String remove(Object key) { throw new UnsupportedOperationException(); }
#Override public void putAll(Map<? extends String, ? extends String> t) { throw new UnsupportedOperationException(); }
}
And then use the exposer to expose properties to a view:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
<property name="attributesMap">
<map>
<entry key="config">
<bean class="com.my.app.PropertyPlaceholderExposer" />
</entry>
</map>
</property>
</bean>
Then in view, use the exposed properties like this:
${config['settings.some.property']}
This solution has the advantage that you can rely on standard placeholder
implementation injected by the context:property-placeholder tag.
Now as a final note, if you really need a to capture all placeholder properties and their values, you have to pipe them through StringValueResolver to make sure that placeholders work inside the property values as expected. The following code will do that.
package com.my.app;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.StringValueResolver;
public class AppConfig extends PropertyPlaceholderConfigurer implements Map<String, String> {
Map<String, String> props = new HashMap<String, String>();
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
this.props.clear();
for (Entry<Object, Object> e: props.entrySet())
this.props.put(e.getKey().toString(), e.getValue().toString());
super.processProperties(beanFactory, props);
}
#Override
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
super.doProcessProperties(beanFactoryToProcess, valueResolver);
for(Entry<String, String> e: props.entrySet())
e.setValue(valueResolver.resolveStringValue(e.getValue()));
}
// Implement map interface to access stored properties
#Override public Set<String> keySet() { return props.keySet(); }
#Override public Set<java.util.Map.Entry<String, String>> entrySet() { return props.entrySet(); }
#Override public Collection<String> values() { return props.values(); }
#Override public int size() { return props.size(); }
#Override public boolean isEmpty() { return props.isEmpty(); }
#Override public boolean containsValue(Object value) { return props.containsValue(value); }
#Override public boolean containsKey(Object key) { return props.containsKey(key); }
#Override public String get(Object key) { return props.get(key); }
#Override public void clear() { throw new UnsupportedOperationException(); }
#Override public String put(String key, String value) { throw new UnsupportedOperationException(); }
#Override public String remove(Object key) { throw new UnsupportedOperationException(); }
#Override public void putAll(Map<? extends String, ? extends String> t) { throw new UnsupportedOperationException(); }
}
I have done this and it has worked.
Properties props = PropertiesLoaderUtils.loadAllProperties("my.properties");
PropertyPlaceholderConfigurer props2 = new PropertyPlaceholderConfigurer();
props2.setProperties(props);
That should work.
CREDIT: Programmatic access to properties in Spring without re-reading the properties file
I've found a nice implementation of accessing the properties programmatically in spring without reloading the same properties that spring has already loaded. [Also, It is not required to hardcode the property file location in the source]
With these changes, the code looks cleaner & more maintainable.
The concept is pretty simple. Just extend the spring default property placeholder (PropertyPlaceholderConfigurer) and capture the properties it loads in the local variable
public class SpringPropertiesUtil extends PropertyPlaceholderConfigurer {
private static Map<String, String> propertiesMap;
// Default as in PropertyPlaceholderConfigurer
private int springSystemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
#Override
public void setSystemPropertiesMode(int systemPropertiesMode) {
super.setSystemPropertiesMode(systemPropertiesMode);
springSystemPropertiesMode = systemPropertiesMode;
}
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException {
super.processProperties(beanFactory, props);
propertiesMap = new HashMap<String, String>();
for (Object key : props.keySet()) {
String keyStr = key.toString();
String valueStr = resolvePlaceholder(keyStr, props, springSystemPropertiesMode);
propertiesMap.put(keyStr, valueStr);
}
}
public static String getProperty(String name) {
return propertiesMap.get(name).toString();
}
}
Usage Example
SpringPropertiesUtil.getProperty("myProperty")
Spring configuration changes
<bean id="placeholderConfigMM" class="SpringPropertiesUtil">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="locations">
<list>
<value>classpath:myproperties.properties</value>
</list>
</property>
</bean>
Hope this helps to solve the problems you have
You can also use either the spring utils, or load properties via the PropertiesFactoryBean.
<util:properties id="myProps" location="classpath:com/foo/myprops.properties"/>
or:
<bean id="myProps" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:com/foo/myprops.properties"/>
</bean>
Then you can pick them up in your application with:
#Resource(name = "myProps")
private Properties myProps;
and additionally use these properties in your config:
<context:property-placeholder properties-ref="myProps"/>
This is also in the docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#xsd-config-body-schemas-util-properties
Create a class like below
package com.tmghealth.common.util;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
#Component
#Configuration
#PropertySource(value = { "classpath:/spring/server-urls.properties" })
public class PropertiesReader extends PropertyPlaceholderConfigurer {
#Override
protected void processProperties(
ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
super.processProperties(beanFactory, props);
}
}
Then wherever you want to access a property use
#Autowired
private Environment environment;
and getters and setters then access using
environment.getProperty(envName
+ ".letter.fdi.letterdetails.restServiceUrl");
-- write getters and setters in the accessor class
public Environment getEnvironment() {
return environment;
}`enter code here`
public void setEnvironment(Environment environment) {
this.environment = environment;
}
You can get your properties through Environment class. As documentation stands:
Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Having Environment as a env variable, simply call:
env.resolvePlaceholders("${your-property:default-value}")
You can get your 'raw' properties through:
env.getProperty("your-property")
It will search through all properties source that spring has registered.
You can either obtain Environment through:
inject ApplicationContext by implementing ApplicationContextAware and then call getEnvironment() on context
implement EnvironmentAware.
It's obtain through implementation of a class because properties are resolved on early stage of application startup, as they may be required for bean construction.
Read more on documentation: spring Environment documentation
As you know the newer versions of Spring don't use the PropertyPlaceholderConfigurer and now use another nightmarish construct called PropertySourcesPlaceholderConfigurer. If you're trying to get resolved properties from code, and wish the Spring team gave us a way to do this a long time ago, then vote this post up! ... Because this is how you do it the new way:
Subclass PropertySourcesPlaceholderConfigurer:
public class SpringPropertyExposer extends PropertySourcesPlaceholderConfigurer {
private ConfigurableListableBeanFactory factory;
/**
* Save off the bean factory so we can use it later to resolve properties
*/
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
super.processProperties(beanFactoryToProcess, propertyResolver);
if (beanFactoryToProcess.hasEmbeddedValueResolver()) {
logger.debug("Value resolver exists.");
factory = beanFactoryToProcess;
}
else {
logger.error("No existing embedded value resolver.");
}
}
public String getProperty(String name) {
Object propertyValue = factory.resolveEmbeddedValue(this.placeholderPrefix + name + this.placeholderSuffix);
return propertyValue.toString();
}
}
To use it, make sure to use your subclass in your #Configuration and save off a reference to it for later use.
#Configuration
#ComponentScan
public class PropertiesConfig {
public static SpringPropertyExposer commonEnvConfig;
#Bean(name="commonConfig")
public static PropertySourcesPlaceholderConfigurer commonConfig() throws IOException {
commonEnvConfig = new SpringPropertyExposer(); //This is a subclass of the return type.
PropertiesFactoryBean commonConfig = new PropertiesFactoryBean();
commonConfig.setLocation(new ClassPathResource("META-INF/spring/config.properties"));
try {
commonConfig.afterPropertiesSet();
}
catch (IOException e) {
e.printStackTrace();
throw e;
}
commonEnvConfig.setProperties(commonConfig.getObject());
return commonEnvConfig;
}
}
Usage:
Object value = PropertiesConfig.commonEnvConfig.getProperty("key.subkey");
This help me:
ApplicationContextUtils.getApplicationContext().getEnvironment()
Here is another sample .
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
cfg.postProcessBeanFactory(factory);
This will resolve any nested properties.
public class Environment extends PropertyPlaceholderConfigurer {
/**
* Map that hold all the properties.
*/
private Map<String, String> propertiesMap;
/**
* Iterate through all the Property keys and build a Map, resolve all the nested values before building the map.
*/
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException {
super.processProperties(beanFactory, props);
propertiesMap = new HashMap<String, String>();
for (Object key : props.keySet()) {
String keyStr = key.toString();
String valueStr = beanFactory.resolveEmbeddedValue(placeholderPrefix + keyStr.trim() + DEFAULT_PLACEHOLDER_SUFFIX);
propertiesMap.put(keyStr, valueStr);
}
}
/**
* This method gets the String value for a given String key for the property files.
*
* #param name - Key for which the value needs to be retrieved.
* #return Value
*/
public String getProperty(String name) {
return propertiesMap.get(name).toString();
}
This post also explatis howto access properties: http://maciej-miklas.blogspot.de/2013/07/spring-31-programmatic-access-to.html
You can access properties loaded by spring property-placeholder over such spring bean:
#Named
public class PropertiesAccessor {
private final AbstractBeanFactory beanFactory;
private final Map<String,String> cache = new ConcurrentHashMap<>();
#Inject
protected PropertiesAccessor(AbstractBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public String getProperty(String key) {
if(cache.containsKey(key)){
return cache.get(key);
}
String foundProp = null;
try {
foundProp = beanFactory.resolveEmbeddedValue("${" + key.trim() + "}");
cache.put(key,foundProp);
} catch (IllegalArgumentException ex) {
// ok - property was not found
}
return foundProp;
}
}
This is the finest way I got it to work:
package your.package;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
public class ApplicationProperties {
private Properties properties;
public ApplicationProperties() {
// application.properties located at src/main/resource
Resource resource = new ClassPathResource("/application.properties");
try {
this.properties = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException ex) {
Logger.getLogger(ApplicationProperties.class.getName()).log(Level.SEVERE, null, ex);
}
}
public String getProperty(String propertyName) {
return this.properties.getProperty(propertyName);
}
}
create .properties file in classpath of your project and add path configuration in xml`<context:property-placeholder location="classpath*:/*.properties" />`
in servlet-context.xml after that u can directly use your file everywhere
Please use the below code in your spring configuration file to load the file from class path of your application
<context:property-placeholder
ignore-unresolvable="true" ignore-resource-not-found="false" location="classpath:property-file-name" />
I know this is an old thread, however, this topic in my opinion becomes of great importance for those using the functional approach for all those usecases where you need a microservice that loads "instantly" and therefore you avoid using annotations.
The problem that remained unsolved was to load eventually the environment variables which I had in my application.yml.
public class AppPropsLoader {
public static Properties load() {
var propPholderConfig = new PropertySourcesPlaceHolderConfigurer();
var yaml = new YamlPropertiesFactoryBean();
ClassPathResource resource = new ClassPathResource("application.yml");
Objects.requireNonNull(resource, "File application.yml does not exist");
yaml.setResources(resource);
Objects.requireNonNull(yaml.getObject(), "Configuration cannot be null");
propPholderConfig.postProcessBeanFactory(new DefaultListableBeanFactory());
propPholderConfig.setProperties(yaml.getObject());
PropertySources appliedPropertySources =
propPholderConfig.getAppliedPropertySources();
var resolver = new PropertySourcesPlaceholderResolver(appliedPropertySources);
Properties resolvedProps = new Properties();
for (Map.Entry<Object, Object> prop: yaml.getObject().entrySet()) {
resolvedProps.setProperty((String)prop.getKey(),
getPropertyValue(resolver.resolvePlaceHolders(prop.getValue()));
}
return resolvedProps;
}
static String getPropertyValue(Object prop) {
var val = String.valueOf(prop);
Pattern p = Pattern.compile("^(\\$\\{)([a-zA-Z0-9-._]+)(\\})$");
Matcher m = p.matcher(val);
if(m.matches()) {
return System.getEnv(m.group(2));
}
return val;
}
}

Resources