I am trying to hook up a messageSource in spring to use for my application. Its not working, gives this error:
org.springframework.context.NoSuchMessageException: No message found under code 'validation_required' for locale 'en'.
my applicationContext.xml contains this def for messageSource:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:messages</value>
</list>
</property>
</bean>
my messages properties file lives in:
/WEB-INF/classes/messages/messages_en_US.properties
Finally, the call i made that generates the error is:
String message = messageSource.getMessage("validation_required", null, Locale.ENGLISH);
Can anyone help me at this hour?
It seems like your path is not correct.
since you have your bundle under /WEB-INF/classes/messages/messages_en_US.properties, your basename setting should look like: classpath:messages/messages (basename in this case means path and properties file prefix).
The problem is with the way you have defined the resource bundle and the locale you are specifying (They doesn't match with the resource bundle's search order. Either rename your bundle to "messages_en.properties" or invoke the "getMessage(...)" with new Locale("en","US"). I prefer the first option.
I use following bean and it is working fine without specifying the path to the file:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" abstract="false"
scope="singleton" lazy-init="default">
<property name="basename" value="messages"/>
</bean>
Although the files I use are simply called "messages_en.properties" and "messages_es.properties" well you get the idea.
When you call
messageSource.getMessage("validation_required", null, null);
do you get an exception? Try to use this file name messages_en.properties or messages_us_en.properties
Try this
look at the comment for getting the string
package yours;
import java.util.Locale;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
*
* Permet la recuperation des String du LocaleContextHolder hors des jsp
* Les langues sont placées dans main/ressources/messagesSources.properties
*
* Example : new MSG("recuperation_name_invalid").value()
*
*/
#Configurable
public class MSG
{
private String key;
#Resource(name = "messageSource")
private MessageSource messageSource;
public MSG(String key)
{
super();
this.key = key;
}
public String value()
{
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(key, new Object[0], locale);
}
#Override
public String toString()
{
return value();
}
}
Related
I am building a web application using spring MVC which is connected to the database. This is part of my bean config file.
<!-- Define Database DataSource / connection pool -->
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/java_task?useSSL=false" />
<property name="user" value="me" />
<property name="password" value="me" />
<!-- these are connection pool properties for C3P0 -->
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<property name="maxIdleTime" value="30000" />
</bean>
<!-- Define Hibernate session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="packagesToScan" value="com.javatask.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
As you can see my data source is defined in a hard way. In code I am using #Autowired annotation to inject sessionFactory. But I would like to inject sessionFactory (jdbc, username, password etc.) with data which I will obtain from user during run time. For example he will write me those data to the textfeild and then I will create sessionFactory (based on his data) which will be connected to his database.
I was looking for an answer but unluckily I have not found anything that would fit to this problem.
There are multiple ways that spring bean definition can be changed in run time. One way is to refresh the Spring Application Context in run time which would have the bean definition specific to the functionality rather than all bean definition.
((ConfigurableApplicationContext)applicationContext).refresh();
Let me explain using one simple use case:
Create a PropertyBean which will load value from external properties file.
Create PropertiesApplicationContext which would have only PropertyBean definition config rather than all beans belongs the application.
Use ConfigurableApplicationContext refresh() method to reload ProperiesBean definition in run time.
More details about below example code snippet:
Web Application should display a Property Value from a bean which
will read the value from a external property file.
Property value will change anytime in external properties file.
Web Application should not restarted.
Here, bean definition should be changed in run time.
Spring Boot Application Code:
#SpringBootApplication(scanBasePackages = {"vijay.controller","vijay.configuration"})
public class SpringContextRefreshApplication extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringContextRefreshApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringContextRefreshApplication.class, args);
}
}
MVC Configuration:
package vijay.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
/**
* MVC Configuration
* #author Vijay
*/
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
registry.viewResolver(resolver);
}
}
Custom (Property) Application Context Aware:
package vijay.configuration;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* PropertyApplicationContext Class to load Property Configuration XML.
* #author Vijay
*/
public class PropertyApplicationContext implements ApplicationContextAware{
private ApplicationContext applicationContext;
private static PropertyApplicationContext propertyApplicationContext;
private PropertyApplicationContext(){
applicationContext = new ClassPathXmlApplicationContext("property-config.xml");
}
#Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
if(applicationContext == null){
this.applicationContext = ac;
}
}
public ApplicationContext getApplicationContext(){
return applicationContext;
}
public static PropertyApplicationContext getInstance(){
if(propertyApplicationContext == null){
propertyApplicationContext = new PropertyApplicationContext();
}
return propertyApplicationContext;
}
}
MVC Controller:
package vijay.controller;
import vijay.beans.PropertyDto;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import vijay.configuration.PropertyApplicationContext;
/**
* Property List Controller.
* #author Vijay
*/
#Controller
public class PropertyController {
#RequestMapping("/")
public String testController(){
System.out.println("vijay.controller.PropertyController.testController()");
return "sample";
}
#RequestMapping(path = "/getProperties")
public String testPropertiesController(ModelMap modelMap){
ApplicationContext applicationContext = PropertyApplicationContext.getInstance().getApplicationContext();
PropertyDto propertyDto = (PropertyDto) applicationContext.getBean("propertyBean");
System.out.println("vijay.controller.PropertyController.testPropertiesController() " + propertyDto);
modelMap.addAttribute("message", propertyDto.getKey());
return "sample";
}
/**
* Method will refresh ApplicationContext
* #param modelMap as ModelMap
* #return viewName as String
*/
#RequestMapping(path = "/refreshProperties")
public String refreshBean(ModelMap modelMap){
ApplicationContext applicationContext = PropertyApplicationContext.getInstance().getApplicationContext();
// Refresh Application Context
((ConfigurableApplicationContext)applicationContext).refresh();
PropertyDto propertyDto = (PropertyDto) applicationContext.getBean("propertyBean");
System.out.println("vijay.controller.PropertyController.refreshBean()" + propertyDto);
modelMap.addAttribute("message", propertyDto.getKey());
return "sample";
}
}
Spring Bean (Property-config.xml) definition File:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:C://sample.properties" />
</bean>
<context:annotation-config/>
<context:component-scan base-package="vijay.beans"/>
</beans>
View JSP (sample.jsp):
<!DOCTYPE html>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html lang="en">
<body>
<div>
<div>
<h1>Spring Boot JSP Example</h1>
<h2>Property Value :: ${message} !</h2>
</div>
</div>
</body>
</html>
Here, MVC Controller has /refreshProperties which will refresh the PropertiesBean defined in PropertiesApplicationContext. As properties-config.xml the only bean definition defined in PropertiesApplicationContext, propertiesBean alone will be refreshed and no other bean definition will be refreshed.
Similar way, you can create Application Context by extending ApplicationContextAware which will have bean definition of sessionFactory. You can get SessionFactory wherever required from this context and refresh the context wherever required.
This is one way to achieve the bean definition change in run time.
Did you try this [Change SessionFactory datasource jdbcurl late in runtime] (Change SessionFactory datasource jdbcurl late in runtime)?
Problem description: I have trouble setting up a mock for a particular spring bean to return the correct mock test resource location on my development box, rather than the runtime web application root. I am sure I am doing something silly with my refactoring. I hope someone sees it.
I am using Quartz to execute a job over Spring. The Quartz job is working fine and picking up the Spring applicationcontext ok. The only thing left is to wire up a configuration property for the test or runtime location where the web resources are located. :
JUnit 4, Spring 3.1, Quartz, Java 8, Mockito
The interface:
public interface ITitle {
/**
* Gets the root of the application.
* #return location String
*/
public String getRuntimeLocation();
}
The implementation:
#Component("Title")
public class Title implements ITitle, ApplicationContextAware {
private String location;
/**
* Gets the root of the application.
* #return location String
*/
public String getRuntimeLocation() {
String location = "";
config = getConfig();
log.debug("Class Title --- Method getRuntimeLocation -- config is " + config );
location = config.getRuntimeLocation();
log.debug("Class Title --- Method getRuntimeMethod -- runtime location is " + location );
return location;
}
}
The Unit Test
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.generators.analytics.serialised.ITitle;
import java.io.File;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"classpath*:/WEB-INF/conf/analytics-context.xml"
})
public class GeneratorTest {
private AnalyticsGenerator generator;
#Mock
private IConfig config;
#Mock
private ApplicationContext applicationContext;
#Mock
private ITitle title;
// Set a logger
private static Logger log = LoggerFactory.getLogger(GeneratorTest.class);
private JobExecutionContext job;
/**
* Initialises the test parameters.
*/
#Before
public void setUp() throws Exception {
//create a generator object
generator = new AnalyticsGenerator();
MockitoAnnotations.initMocks(this);
when(applicationContext.getBean("Query")).thenReturn(query);
when(applicationContext.getBean("Config")).thenReturn(config);
when(config.getRuntimeLocation()).thenReturn(“/Users/me/dev/workarea/");
generator.executeInternal(ctx);
}
/**
* Expected: Json exists
*/
#Test
public void testThatExecuteInternalCreatesAJsonFile() throws JobExecutionException {
generator.executeInternal(job);
File returnedJson = new File("classpath:/google-analytics.json");
Assert.assertNotNull("The JSON file does not exist", returnedJson );
}
/**
* Remove objects from memory.
*/
#After
public void tearDown() throws Exception {
generator = null;
}
}
The spring xml file
<?xml version="1.0" encoding="UTF-8"?>
<!--
* analytics-context.xml
*
* resource configuration file for Google Analytics recommendations integration framework.
*
* All custom beans and services must be defined in this file.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring
http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">
<context:annotation-config/>
<!--
START Globally used Google Analytics Query parameters
-->
<bean id="Config" class=“com.generators.analytics.Config">
<property name="reportLocation" value="/data/runtime/web/assets/generated-list/google-analytics.json"/>
<property name="runtimeLocation" value="/data/runtime/web/"/>
</bean>
<bean id="configFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface" value=“com.generators.analytics.ConfigFactory" />
</bean>
<alias name="Config" alias="C" />
<bean id="Title" class=“com.generators.analytics.serialised.Title">
<property name="config" ref="Config"/>
</bean>
<context:component-scan base-package=“com.generators.analytics" />
<!--
END Globally used Google Analytics Query parameters
-->
After running the test I get this in the log:
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getConfig -- applicationContext is org.springframework.context.support.GenericApplicationContext#57f23557: startup date [Tue Jan 24 11:51:31 GMT 2017]; root of context hierarchy
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getConfig -- config is com.generators.analytics.Config#13d9cbf5
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getRuntimeLocation -- config is com.generators.analytics.Config#13d9cbf5
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getRuntimeMethod -- runtime location is /data/runtime/web/
The question is, is there anything obvious that I do wrong to get the intended path /Users/me/dev/workarea/ ?
I guess I need to do refactoring to extract the location from the method ? I am not sure how to refactor this particular step.
when(config.getRuntimeLocation()).thenReturn(“/Users/me/dev/workarea/");
I resolved this problem by creating a copy of the spring configuration file and changing
<bean id="Config" class="com.generators.analytics.Config">
<property name="reportLocation" value="/Users/arnout/dev/soton-test/workarea/assets/generated-list/google-analytics.json"/>
<property name="runtimeLocation" value="/Users/arnout/dev/soton-test/workarea/"/>
</bean>
and then changing
#ContextConfiguration(locations = {
"classpath*:/WEB-INF/conf/livesite_customer/resources/test-analytics-resource-config.xml"
})
public class GeneratorTest {
I did some lateral thinking after I read up on Profiles in Spring configurations. In fact I did not need a profile, it was just as easy as taking another spring configuration in test mode.
This might sound like a novice question. I want to inject datasource properties (which I am getting at runtime) and inject it to the bean..
I have a method in my javaclass...
public <String,String>map myMethod(Map<String, String> model) {
Map mapA = new HashMap();
mapA.put("username", "element 1");
mapA.put("password", "element 2");
mapA.put("host", "element 3");
return map;
}
I want to inject these values to my datasource bean in application-context.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value=""/> // inject values here
<property name="url" />
<property name="username" />
<property name="password" />
</bean>
I have seen numerous example on injecting values to beans using properties file but I could not figure out on how to inject a value from java class to the bean properties.
Thanks
You need to create a #Configuration class with a method annotated with #Bean returning an instance of org.apache.commons.dbcp.BasicDataSource.
#Configuration
public class DatasourceConfiguration {
#Bean
public BasicDataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(""); // you can call your code here
ds.setUrl(""); // to get these configuration values
ds.setUsername("");
ds.setPassword("");
return ds;
}
}
It can be a not so elegant solution, but what about this approach?
You can try to return a String from your method.
#Configuration
public class DatasourceConfiguration2 {
#Bean
public String getDataSourceSetting() {
Map<String, String> map = myMethod(model); //assuming that you are not able to edit the original method
StringBuilder sb = new StringBuilder();
for (Entry<String, String> e : map.entrySet()) {
sb.append(e.getKey()).append('=').append(e.getValue()).append(';');
}
}
}
In your xml you can define the property like:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="connectionProperties" value="dataSourceSetting"/>
</bean>
Based on dbcp api:
The "user" and "password" properties will be added explicitly, so they do not need to be included here.
Checking the source code you can see if user and password are null a message like log("DBCP DataSource configured without a 'username'"); will be printed. But the property will be available there.
Finally, in case of url property, there is no option, you need to set it up explicitly.
there is a way to hide/encrypt password in xml spring config file?
I read that is possible with a "custom" subclass of DataSource, but the solutions keep key in same config file as plain text...so is a bit useless.
There is a way to use KeyStore for this?
For example read the value from a keystore.
Thanks all.
What is the purpose of hiding the password? I suggest you configure the datasource in the container (Tomcat, JBoss or whatever you use) and inject the datasource into your application using jndi:
<jee:jndi-lookup id="thedatasource"
jndi-name="java:comp/env/jdbc/thedatasource"
lookup-on-startup="false"
expected-type="javax.sql.DataSource"/>
This way you have not to expose and password in your application but only in the servlet container.
Yes, you can do that. You will have to create a wrapper bean around the data source class. Here is an example of how I have done it before. Hope this helps!
<beans>
<bean id="someDao" class="com.dao.SomeDAOImpl">
<property name="datasource">
<ref local="secureDataSource"/>
</property>
</bean>
<bean id="secureDataSource" class="com.ds.SecureDataSource">
<property name="driverClassName">
<value><your driver></value>
</property>
<property name="url">
<value><your url></value>
</property>
<property name="username">
<value><your user id></value>
</property>
<property name="password">
<value><encrypted_pwd></value>
</property>
</bean>
</beans>
Then inside the SecureDataSource class you will need to decrypt the password.
import java.sql.Connection;
import java.sql.SQLException;
public class SecureDataSource extends DriverManagerDataSource{
private String url;
private String username;
private String password;
/**
* #param url the url to set
*/
public void setUrl(String url) {
this.url = url;
}
/**
* #param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* #param password the password to set
*/
public void setPassword(String password) {
this.password = password;
}
protected Connection getConnectionFromDriverManager() throws SQLException {
String decryptedPassword = null;
//decrypt the password here
return getConnectionFromDriverManager(url,username,decryptedPassword);
}
}
Good options have been given, another obvious answer is to use the PropertyPlaceholderConfigurer:
<context:property-placeholder
system-properties-mode="OVERRIDE"
location="classpath:database.properties" />
<bean id="dataSource" class="com.whatever.datasource.you.Use">
<property name="password" value="${database.password}" />
</bean>
Now you can keep your password either as a property in a properties file (which you might create during deployment if you don't want to have it in the SCM) or as a System Property (which will hopefully also be beyond reach of other developers).
Clarification: create during deployment is somewhat vague. I guess you will have to write an installer that generates the properties file dynamically on the end user's machine, probably coupled with a sign up / log in mechanism.
EDIT: I still haven't figured out who you are hiding the information from. Two theories:
a) People who have access to your source code
b) Your customers
If it's a), then go my way. All other ways can easily be breached by the other developer just starting your application with a debugger (and suddenly he's inside the datasource object and sees the password).
If it's b), then you have no chance, basically. The customer has tons of possibilities to get at your password: debuggers, agents, bytecode manipulation, loadtime weaving etc. Even if he doesn't do any of that, he will just have to attach a port sniffer to get at the password in clear text. The only safe thing to do is have a username / password per customer (never store a global password at your customer's machine).
I had the same question recently. I wanted to store a hashed version of the password in a .properties file.
I did the trick thanks to the previous options: I extended the DelegatingDataSource and overrided the getConnection([...]) methods.
public class UnhashingDataSource extends DelegatingDataSource {
private static final Logger LOGGER = Logger.getLogger(UnhashingDataSource.class);
private static final int HEX_RADIX = 16;
private static final String DB_PASS = "a_sample_password";
#Override
public Connection getConnection() throws SQLException {
DriverManagerDataSource dataSource = (DriverManagerDataSource) getTargetDataSource();
return getConnection(dataSource.getUsername(), dataSource.getPassword());
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
try {
DataSource datasource = getTargetDataSource();
if (datasource == null) {
throw new RuntimeException("targetDataSource is null");
}
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(DB_PASS.getBytes());
if (password.equals(getHexString(md.digest()))) {
return datasource.getConnection(username, DB_PASS);
} else {
throw new RuntimeException("Unable to connect to DB");
}
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Unknown algorithm");
}
return null;
}
private String getHexString(final byte[] messageDigest) {
BigInteger bigInt = new BigInteger(1, messageDigest);
return bigInt.toString(HEX_RADIX);
}
}
Then, here is how I used it in my applicationContext.xml:
# Using the unhashing datasource
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="unhashingDataSource" />
# ...
</bean>
<bean id="hashedDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${datasource.driverClassName}" />
<property name="url" value="${datasource.url}" />
<property name="username" value="${datasource.username}" />
<property name="password" value="${datasource.hash}" />
</bean>
<bean id="unhashingDataSource"
class="my.package.UnhashingDataSource">
<property name="targetDataSource" ref="hashedDataSource" />
</bean>
Where datasource.hash is a property (from a .properties file) stored like:
datasource.hash = 2e54b0667ef542e3398c55a08a4e04e69b9769e8
The plain password is still in bytecode but not directly in a .properties file anymore.
Thanks for all your post and queries.
Hope for visitors its clear the technical way to encrypt password by reading this page. One important thing I would like to add here, if you are dealing with production then definitely will suggest you to use any "Secure Hash Algorithm" like SHA-256 with salt. You can consider secure hash algorithm using salt as industry standard.
Spring has a very handy convenience class called PropertyPlaceholderConfigurer, which takes a standard .properties file and injects values from it into your bean.xml config.
Does anyone know of a class which does exactly the same thing, and integrates with Spring in the same way, but accepts XML files for the config. Specifically, I'm thinking of Apache digester-style config files. It would be easy enough to do this, I'm just wondering if anyone already has.
Suggestions?
I just tested this, and it should just work.
PropertiesPlaceholderConfigurer contains a setPropertiesPersister method, so you can use your own subclass of PropertiesPersister. The default PropertiesPersister already supports properties in XML format.
Just to show you the fully working code:
JUnit 4.4 test case:
package org.nkl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#ContextConfiguration(locations = { "classpath:/org/nkl/test-config.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
public class PropertyTest {
#Autowired
private Bean bean;
#Test
public void testPropertyPlaceholderConfigurer() {
assertNotNull(bean);
assertEquals("fred", bean.getName());
}
}
The spring config file test-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
">
<context:property-placeholder
location="classpath:/org/nkl/properties.xml" />
<bean id="bean" class="org.nkl.Bean">
<property name="name" value="${org.nkl.name}" />
</bean>
</beans>
The XML properties file properties.xml - see here for description of usage.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="org.nkl.name">fred</entry>
</properties>
And finally the bean:
package org.nkl;
public class Bean {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Hope this helps...
Found out that Spring Modules provide integration between Spring and Commons Configuration, which has a hierarchial XML configuration style. This ties straight into PropertyPlaceholderConfigurer, which is exactly what I wanted.
Been trying to come up with a nice solution to this myself that
Revolves around creating an XSD for the config file - since in my mind the whole benefit of using XML is that you can strongly type the config file, in terms of datatypes, and which fields are mandatory/optional
Will validate the XML against the XSD, so if a value is missing it'll throw an error out rather than your bean being injected with a 'null'
Doesn't rely on spring annotations (like #Value - in my mind that's giving beans knowledge about their container + relationship with other beans, so breaks IOC)
Will validate the spring XML against the XSD, so if you try to reference an XML field not present in the XSD, it'll throw out an error too
The bean has no knowledge that its property values are being injected from XML (i.e. I want to inject individual properties, and not the XML object as a whole)
What I came up with is as below, apologies this is quite long winded, but I like it as a solution since I believe it covers everything. Hopefully this might prove useful to someone. Trivial pieces first:
The bean I want property values injected into:
package com.ndg.xmlpropertyinjectionexample;
public final class MyBean
{
private String firstMessage;
private String secondMessage;
public final String getFirstMessage ()
{
return firstMessage;
}
public final void setFirstMessage (String firstMessage)
{
this.firstMessage = firstMessage;
}
public final String getSecondMessage ()
{
return secondMessage;
}
public final void setSecondMessage (String secondMessage)
{
this.secondMessage = secondMessage;
}
}
Test class to create the above bean and dump out the property values it got:
package com.ndg.xmlpropertyinjectionexample;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main
{
public final static void main (String [] args)
{
try
{
final ApplicationContext ctx = new ClassPathXmlApplicationContext ("spring-beans.xml");
final MyBean bean = (MyBean) ctx.getBean ("myBean");
System.out.println (bean.getFirstMessage ());
System.out.println (bean.getSecondMessage ());
}
catch (final Exception e)
{
e.printStackTrace ();
}
}
}
MyConfig.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:myconfig="http://ndg.com/xmlpropertyinjectionexample/config" targetNamespace="http://ndg.com/xmlpropertyinjectionexample/config">
<xsd:element name="myConfig">
<xsd:complexType>
<xsd:sequence>
<xsd:element minOccurs="1" maxOccurs="1" name="someConfigValue" type="xsd:normalizedString" />
<xsd:element minOccurs="1" maxOccurs="1" name="someOtherConfigValue" type="xsd:normalizedString" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Sample MyConfig.xml file based on the XSD:
<?xml version="1.0" encoding="UTF-8"?>
<config:myConfig xmlns:config="http://ndg.com/xmlpropertyinjectionexample/config">
<someConfigValue>First value from XML file</someConfigValue>
<someOtherConfigValue>Second value from XML file</someOtherConfigValue>
</config:myConfig>
Snippet of pom.xml file to run xsd2java (wasn't much else in here besides setting to Java 1.6, and spring-context dependency):
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<id>main-xjc-generate</id>
<phase>generate-sources</phase>
<goals><goal>generate</goal></goals>
</execution>
</executions>
</plugin>
Now the spring XML itself. This creates a schema/validator, then uses JAXB to create an unmarshaller to create a POJO from the XML file, then uses spring # annotation to inject property values by quering the POJO:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd" >
<!-- Set up schema to validate the XML -->
<bean id="schemaFactory" class="javax.xml.validation.SchemaFactory" factory-method="newInstance">
<constructor-arg value="http://www.w3.org/2001/XMLSchema"/>
</bean>
<bean id="configSchema" class="javax.xml.validation.Schema" factory-bean="schemaFactory" factory-method="newSchema">
<constructor-arg value="MyConfig.xsd"/>
</bean>
<!-- Load config XML -->
<bean id="configJaxbContext" class="javax.xml.bind.JAXBContext" factory-method="newInstance">
<constructor-arg>
<list>
<value>com.ndg.xmlpropertyinjectionexample.config.MyConfig</value>
</list>
</constructor-arg>
</bean>
<bean id="configUnmarshaller" class="javax.xml.bind.Unmarshaller" factory-bean="configJaxbContext" factory-method="createUnmarshaller">
<property name="schema" ref="configSchema" />
</bean>
<bean id="myConfig" class="com.ndg.xmlpropertyinjectionexample.config.MyConfig" factory-bean="configUnmarshaller" factory-method="unmarshal">
<constructor-arg value="MyConfig.xml" />
</bean>
<!-- Example bean that we want config properties injected into -->
<bean id="myBean" class="com.ndg.xmlpropertyinjectionexample.MyBean">
<property name="firstMessage" value="#{myConfig.someConfigValue}" />
<property name="secondMessage" value="#{myConfig.someOtherConfigValue}" />
</bean>
</beans>
I'm not sure about the Apache digester-style config files, but I found a solution that was not that hard to implement and suitable for my xml config-file.
You can use the normal PropertyPlaceholderConfigurer from spring, but to read your custom config you have to create your own PropertiesPersister, where you can parse the xml (with XPath) and set the required properties yourself.
Here's a small example:
First create your own PropertiesPersister by extending the default one:
public class CustomXMLPropertiesPersister extends DefaultPropertiesPersister {
private XPath dbPath;
private XPath dbName;
private XPath dbUsername;
private XPath dbPassword;
public CustomXMLPropertiesPersister() throws JDOMException {
super();
dbPath = XPath.newInstance("//Configuration/Database/Path");
dbName = XPath.newInstance("//Configuration/Database/Filename");
dbUsername = XPath.newInstance("//Configuration/Database/User");
dbPassword = XPath.newInstance("//Configuration/Database/Password");
}
public void loadFromXml(Properties props, InputStream is)
{
Element rootElem = inputStreamToElement(is);
String path = "";
String name = "";
String user = "";
String password = "";
try
{
path = ((Element) dbPath.selectSingleNode(rootElem)).getValue();
name = ((Element) dbName.selectSingleNode(rootElem)).getValue();
user = ((Element) dbUsername.selectSingleNode(rootElem)).getValue();
password = ((Element) dbPassword.selectSingleNode(rootElem)).getValue();
}
catch (JDOMException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
props.setProperty("db.path", path);
props.setProperty("db.name", name);
props.setProperty("db.user", user);
props.setProperty("db.password", password);
}
public Element inputStreamToElement(InputStream is)
{
...
}
public void storeToXml(Properties props, OutputStream os, String header)
{
...
}
}
Then inject the CustomPropertiesPersister to the PropertyPlaceholderConfigurer in the application context:
<beans ...>
<bean id="customXMLPropertiesPersister" class="some.package.CustomXMLPropertiesPersister" />
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_FALLBACK" />
<property name="location" value="file:/location/of/the/config/file" />
<property name="propertiesPersister" ref="customXMLPropertiesPersister" />
</bean>
</beans>
After that you can use your properties like this:
<bean id="someid" class="some.example.class">
<property name="someValue" value="$(db.name)" />
</bean>