Using values from properties file in spring roo - spring

I'm new to spring and spring-roo. I try to build an application and read some key value pairs from a properties file.
I created a myconfig.properties file and saved it to src/main/resources/META-INF/spring/.
The content of the file is:
## My Configuration settings
myconfig.url=https://1.2.3.4/api.php
myconfig.username=user1
myconfig.password=password1
Now I added a bean configuration into appilcationContext.xml in the same directory
<bean id="MyConfig" class="com.test.client.MyClient">
<property name="url" value="${myconfig.url}" />
<property name="username" value="${myconfig.username}" />
<property name="password" value="${myconfig.password}" />
</bean>
In my class file I tried to access the values, but I get an File not found error
package com.test.client;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
#RooJavaBean
#RooToString
#RooJpaActiveRecord
public class MyClient {
private String url;
private String username;
private String password;
public static String login()
{
// Construct the spring application context
AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyClient config = (MyClient) context.getBean("MyConfig");
// Register hook to shutdown Spring gracefully
// See http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-factory-shutdown
context.registerShutdownHook();
String token = null;
final String url = config.getUrl();
final String username = config.getUsername();
final String password = config.getPassword();
....
Thanks for any help!

Try
AbstractApplicationContext context = new ClassPathXmlApplicationContext("classpath*:META-INF/spring/applicationContext.xml");
However the best practice is to implement interface ApplicationContextAware.
Stefano

Related

SimpleRabbitListenerContainerFactory.setAdviceChain: can MethodInterceptor accept POJO that parameterizes #RabbitListener?

Versions:
org.springframework.amqp:spring-rabbit:2.4.8
openjdk version "11.0.12" 2021-07-20
XML:
<bean id="myAdvice" class="com.acme.interceptors.TrackNewBarcode"/>
<bean id="rmqMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
<constructor-arg ref="myObjectMapper"/>
</bean>
<bean id="rabbitListenerContainerFactory" class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="concurrentConsumers" value="1"/>
<property name="maxConcurrentConsumers" value="1"/>
<property name="receiveTimeout" value="316224000000"/>
<property name="messageConverter" ref="rmqMessageConverter"/>
<property name="adviceChain" ref="myAdvice"/>
</bean>
Java:
package com.acme.tasks;
import com.acme.io.NewBarcodes;
import javax.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Validated
#Component
public class ConsumeBarcodes
{
private static final Logger logger = LoggerFactory.getLogger(ConsumeBarcodes.class);
#Value("${rabbitmq.queue}")
private String queueName;
public ConsumeBarcodes() {}
#RabbitListener(queues = "${rabbitmq.queue}", ackMode = "AUTO")
public void ingestNewBarcodes(#NotNull NewBarcodes newBarcodes)
{
logger.debug("RECEIVED message in ingestNewBarcodes from RabbitMQ queue: {}", queueName);
// XXX Process newBarcodes here.
}
}
Advice:
package com.acme.interceptors;
...
public class TrackNewBarcode implements MethodInterceptor
{
...
#Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable
{
NewBarcodes newBarcodes = (NewBarcodes)methodInvocation.getArguments()[1];
...
}
}
In the examples I have seen for #RabbitListener advices, in invoke(), argument #1 is cast to a Message. But in my #RabbitListener, I rely on a Jackson message-converter to convert the inbound parameter into the POJO NewBarcodes.
QUESTION: in my advice here, can I cast argument #1 to the POJO NewBarcodes?
The simple answer is No. methodInvocation.getArguments() returns a Channel in the first argument (index 0), and a Message in the second argument (index 1). Casting a Message to NewBarcodes would lead to an exception.
Nevertheless, there are still other ways to encode an advice here. I spell out one approach below.
First, per Spring AOP docs, you should enable AOP functionality in your application configuration. Eg:
#Configuration
#EnableAspectJAutoProxy
public class MyWebAppConfig implements WebMvcConfigurer
{
...
}
Annotate ConsumeBarcodes with #Aspect, and set up #Pointcut and #After advice:
#Aspect
#Validated
#Component
public class ConsumeBarcodes
{
private static final Logger logger = LoggerFactory.getLogger(ConsumeBarcodes.class);
#Value("${rabbitmq.queue}")
private String queueName;
public ConsumeBarcodes() {}
#RabbitListener(queues = "${rabbitmq.queue}", ackMode = "AUTO")
public void ingestNewBarcodes(#NotNull NewBarcodes newBarcodes)
{
logger.debug("RECEIVED message in ingestNewBarcodes from RabbitMQ queue: {}", queueName);
// XXX Process newBarcodes here.
}
#Pointcut("execution(com.acme.tasks.ConsumeBarcodes.ingestNewBarcodes(..)")
public void newBarcodesIn() {}
#After("newBarcodesIn()")
public void ackNewBarcodesIn(JoinPoint jp)
{
NewBarcodes newBarcodes = (NewBarcodes)jp.getArgs()[0];
...
}
}
Arguably, this is overkill, as we could simply put logic from ackNewBarcodesIn() inside ingestNewBarcodes() and forgo advices altogether. Nevertheless, I have confirmed this setup works and the #After advice fires as expected.

Spring MVC: Multiple dataSources: select DB schema based on user selection from UI

I'm working on a web app which works fine. But recently from management request we need to serve our web app to multiple teams. So what we decided is to create a separate copy of DB schema for each team.
Let's say our team uses TEAM_A schema & other team uses TEAM_B schema. Both DB schema's are exactly same only difference is data in them.
So I used AbstractRoutingDataSource in my application like this:
package com.company.app.utils.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return TeamContextHolder.getTeamType();
}
}
And TeamContextHolder as:
package com.company.app.utils.datasource;
public class TeamContextHolder{
public static final String TEAM_A_DATA_SOURCE = "aTeamDataSource";
public static final String TEAM_B_DATA_SOURCE = "bTeamDataSource";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setTeamType(String teamType) {
contextHolder.set(teamType);
}
public static String getTeamType() {
return contextHolder.get();
}
public static void clearTeamType() {
contextHolder.remove();
}
}
Spring configuration file have below bean definition for data-sources:
<bean id="aTeamDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="url" value="jdbc:mysql://localhost.com:3306/TEAM_A?autoReconnect=true&verifyServerCertificate=false&useSSL=true" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="welcome" />
<property name="password" value="welcome" />
</bean>
<bean id="bTeamDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="url" value="jdbc:mysql://localhost.com:3306/TEAM_B?autoReconnect=true&verifyServerCertificate=false&useSSL=true" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="welcome" />
<property name="password" value="welcome" />
</bean>
<bean primary="true" id="dynamicDataSource" class="com.company.app.utils.datasource.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="aTeamDataSource" key="aTeamDataSource"></entry>
<entry value-ref="bTeamDataSource" key="bTeamDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="aTeamDataSource" >
</property>
</bean>
My home controller:
package com.company.app.web;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.Map;
#Controller
#SessionAttributes("team")
public class Home {
/**
* Logger
*/
private static final Logger logger = Logger.getLogger(Home.class);
#RequestMapping(value="/home")
public ModelAndView mainHomePage() {
logger.info("Landing on main home page");
Map<String, Object> model = new HashMap<>();
return new ModelAndView("/home", "model", model);
}
#RequestMapping(value="/setteam")
public ModelAndView rememberTeam(#RequestParam String product) {
logger.info("Setting product info: " + product);
Map<String, Object> model = new HashMap<>();
model.put("team", team);
return new ModelAndView("/home", "model", model);
}
}
From home controller, I'm passing team session variable to other controllers via JSP file.
My home.jsp has below form:
<form action="./setproduct" method="POST" class="p-a-4">
<fieldset class="page-signup-form-group form-group form-group-lg">
<select class="page-signup-form-control form-control" id="grid-input-lg-2" name="product">
<option value="Not selected">Not selected</option>
<option value="TEAM_A">TEAM_A</option>
<option value="TEAM_B">TEAM_B</option>
</select>
</fieldset>
<button type="submit" class="btn btn-block btn-lg btn-primary m-t-3">Submit</button>
</form>
And then finally I'm setting datasource to use in my DaoImpl classes as:
if (filters.getTeam().equalsIgnoreCase("TEAM_B")){
TeamContextHolder.setTeamType(TeamContextHolder.TEAM_B_DATA_SOURCE);
}
And it works as expected as I thought. But blown in production as all the time TEAM_B datasource is selected.
So my questions:
Is it possible the on single tomcat webserver (where we deployed our
app) few users can select TEAM_A and work with TEAM_A_DATA_SOURCE
and few users select TEAM_B and work with TEAM_B_DATA_SOURCE?
Initially I thought it is doable but after this kaboom on production
I'm not sure if it's possible in spring.
If it's NOT possible then
how should I handle my case where end users can select their teams
and work simultaneous on two different DB schema's on same web-app.
I resolved this issue by changing contextHolder from ThreadLocal to HttpSession, that way I was able to connect to a team's DB schema for a entire browser session. Also now I don't have to pass session variable to other controllers and no need to set datasource to use in my DaoImpl classes as I was doing earlier. So now my updated TeamContextHolder looks like this:
package com.company.app.utils.datasource;
import org.apache.log4j.Logger;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpSession;
public class TeamContextHolder {
private static final Logger logger = Logger.getLogger(TeamContextHolder.class);
public static HttpSession getCurrentSession() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(true);
}
public static void setTeamType(String teamType) {
TeamContextHolder.getCurrentSession().setAttribute("team", teamType);
}
public static String getTeamType() {
logger.info("Session attribute product: " + TeamContextHolder.getCurrentSession().getAttribute("team"));
return (String) TeamContextHolder.getCurrentSession().getAttribute("team");
}
}
Session variable is set from home controller as:
package com.company.app.web;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
#Controller
#SessionAttributes("team")
public class HomeController {
private static final Logger logger = Logger.getLogger(HomeController.class);
#RequestMapping(value="/home")
public ModelAndView home() {
logger.info("Landing on main home page");
Map<String, Object> model = new HashMap<>();
return new ModelAndView("/home", "model", model);
}
#RequestMapping(value="/setteam")
public ModelAndView setteam(#RequestParam String team,
HttpServletRequest request) {
// Setting session attribute:
request.getSession().setAttribute("team", team);
logger.info("Setting team: " + team);
Map<String, Object> model = new HashMap<>();
model.put("team", team);
return new ModelAndView("/home", "model", model);
}
}
You can have multiple entity managers which will be scanning the different set of repositories and you can switch those entity managers depending upon your cookie.

Spring configuration class to inject bean as property

I am having a spring configuration class which I am using to read from properties file and create the beans.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#PropertySource("classpath:conf.properties")
public class ApplicationConfiguration {
#Value("${name}")
private String username;
#Value("${password}")
private String password;
#Value("${ethnicity}")
private String ethnicity;
#Bean
public Employee employee(){
Employee emp = new Employee();
ConfigParam configParam = new ConfigParam();
configParam.setEthnicity(ethnicity);
emp.setConfigParam(configParam);
return emp;
}
}
Inside the xml file
<property name="configParam">
<bean class="com.test.config.ConfigParam">
<property name="ethnicity" value="indian" />
</bean>
</property>
I am able to set the username and password properties but unable to set the configParam attribute to employee since we need to inject the ConfigParamand its a bean. Please let me know how to inject a bean inside the employee method.
Assuming that your Spring XML context that includes the configParam definition is called my-spring-context.xml and is found in a resources/spring folder, in other words in a spring folder in your classpath (name and folder are arbitrary):
Make your ApplicationConfiguration configuration class aware of that XML context file by using the #ImportResource annotation:
#Configuration
#ImportResource("classpath:spring/my-spring-context.xml")
#PropertySource("classpath:conf.properties")
public class ApplicationConfiguration {
//...
}
Then in your employee method dependency-inject the XML bean as usual. If you want to inject by name and not by type you can add the #Qualifier("myBeanName") directly prior the configParam argument definition.
#Bean
#Autowired
public Employee employee(ConfigParam configParam){
Employee emp = new Employee();
configParam.setEthnicity(ethnicity);
emp.setConfigParam(configParam);
return emp;
}

How to pass a Map<String, String> with application.properties

I have implemented some authorization in a webservice that needs be configured.
Currently the user/password combo is hardcoded into the bean configuration. I would like to configure the map with users and passwords into the application.properties so the configuration can be external.
Any clue on how this can be done?
<bean id="BasicAuthorizationInterceptor" class="com.test.BasicAuthAuthorizationInterceptor">
<property name="users">
<map>
<entry key="test1" value="test1"/>
<entry key="test2" value="test2"/>
</map>
</property>
</bean>
you can use #Value.
Properties file:
users={test1:'test1',test2:'test2'}
Java code:
#Value("#{${users}}")
private Map<String,String> users;
You can use #ConfigurationProperties to have values from application.properties bound into a bean. To do so you annotate your #Bean method that creates the bean:
#Bean
#ConfigurationProperties
public BasicAuthAuthorizationInterceptor interceptor() {
return new BasicAuthAuthorizationInterceptor();
}
As part of the bean's initialisation, any property on BasicAuthAuthorizationInterceptor will be set based on the application's environment. For example, if this is your bean's class:
public class BasicAuthAuthorizationInterceptor {
private Map<String, String> users = new HashMap<String, String>();
public Map<String, String> getUsers() {
return this.users;
}
}
And this is your application.properties:
users.alice=alpha
users.bob=bravo
Then the users map will be populated with two entries: alice:alpha and bob:bravo.
Here's a small sample app that puts this all together:
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
public class Application {
public static void main(String[] args) throws Exception {
System.out.println(SpringApplication.run(Application.class, args)
.getBean(BasicAuthAuthorizationInterceptor.class).getUsers());
}
#Bean
#ConfigurationProperties
public BasicAuthAuthorizationInterceptor interceptor() {
return new BasicAuthAuthorizationInterceptor();
}
public static class BasicAuthAuthorizationInterceptor {
private Map<String, String> users = new HashMap<String, String>();
public Map<String, String> getUsers() {
return this.users;
}
}
}
Take a look at the javadoc for ConfigurationProperties for more information on its various configuration options. For example, you can set a prefix to divide your configuration into a number of different namespaces:
#ConfigurationProperties(prefix="foo")
For the binding to work, you'd then have to use the same prefix on the properties declared in application.properties:
foo.users.alice=alpha
foo.users.bob=bravo
A java.util.Properties object is already a Map, actually a HashTable which in turn implements Map.
So when you create a properties file (lets name it users.properties) you should be able to load it using a PropertiesFactoryBean or <util:properties /> and inject it into your class.
test1=test1
test2=test2
Then do something like
<util:properties location="classpath:users.properties" id="users" />
<bean id="BasicAuthorizationInterceptor" class="com.test.BasicAuthAuthorizationInterceptor">
<property name="users" ref="users" />
</bean>
Although if you have a Map<String, String> as a type of the users property it might fail... I wouldn't put them in the application.properties file. But that might just be me..
I think you are looking for something similar
http://www.codejava.net/frameworks/spring/reading-properties-files-in-spring-with-propertyplaceholderconfigurer-bean
You can pick values from .properties similarly and assign it to your map.

Spring resource class loading from an external folder

I have a spring project name: SpringExample‬
the Directory Structure:
the locale.xml for the spring :
<bean id="customerService" class="com.mkyong.customer.services.CustomerService" />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>\SpringExample\conf‬\messages</value>
</property>
</bean>
the code for CustomerService:
package com.mkyong.customer.services;
public class CustomerService implements MessageSourceAware
{
private MessageSource messageSource;
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public void printMessage(){
String name = messageSource.getMessage("customer.name",
new Object[] { 28, "http://www.mkyong.com" }, Locale.US);
System.out.println("Customer name (English) : " + name);
}
the code for the app class (the main class):
package com.mkyong.common;
import java.util.Locale;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mkyong.customer.services.CustomerService;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "locale.xml" });
// get the message resource inside context
String name = context.getMessage("customer.name", new Object[] { 28,
"http://www.mkyong.com" }, Locale.US);
System.out.println("Customer name (English) : " + name);
// get the message resource inside the bean
CustomerService cust = (CustomerService) context
.getBean("customerService");
cust.printMessage();
}
}
But it fails to bundle the proprtie file, because it isn't in the package.
i get the exception:
WARNING: ResourceBundle [\SpringExample\conf?\messages] not found for
MessageSource: Can't find bundle for base name
\SpringExample\conf?\messages, locale en_US
how can i get it to bundle with external folder resource like in my example?
Make the conf directory a maven resource directory (maven build helper plugin) or move the files from conf to src\main\resources. Then modify the ResourceBundleMessageSource to load the messages form classpath root, because src\main\resources correspond to the classpath root.
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>messages</value>
</property>
</bean>
Attention: If you use ReloadableResourceBundleMessageSource instead of ResourceBundleMessageSource then you have to use the prefix classpath: for the basename propertz value (classpath:messages)

Resources