Spring AOP: applying properties through the aspect - spring

The intent here is to deal with obfuscated passwords for resources.
We have an Advisor that intercepts calls to setPassword and decrypts the argument.
We've set up a template that looks somewhat like this:
<bean id="pwAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice"><bean class="our.advice.bean.class"/></property>
<property name="mappedName" value="setPassword"/>
</bean>
<bean id="passwordHandlerTemplate" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
<property name="interceptorNames"><list><value>pwAdvisor</value></list></property>
</bean>
I'm unclear on the exact syntax to use it. The most obvious way is:
<bean id="myPasswordProtectedThing" parent="passwordHandlerTemplate">
<property name="target">
<bean class="the.target.class.name">
<property name="password" value="encrypted garbage"/>
</bean>
</property>
</bean>
But that doesn't work right, since the password property is applied to the inner bean, which means that the advisor won't wind up doing its work.
Well, what about this:
<bean id="myPasswordProtectedThing" parent="passwordHandlerTemplate">
<property name="target"><bean class="the.target.class.name"/></property>
<property name="password" value="encrypted garbage"/>
</bean>
Nope. Spring complains that the ProxyFactoryBean doesn't have a password property. And, of course, it doesn't. The thing that has the password property is the thing the factory bean creates.
Bueller?

My first effort was poor, but I was in hurry. I apologize. Now I think I know how it should work, because I believe I've implemented what you want myself.
I started with a Credential class (note: no interface):
package aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Credential
{
private static final String DEFAULT_USERNAME = "username";
private static final String DEFAULT_PASSWORD = "password";
private String username;
private String password;
public static void main(String[] args)
{
Credential cred1 = new Credential("foo", "bar");
System.out.println("created using new: " + cred1);
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:aop-context.xml");
Credential cred2 = (Credential) context.getBean("credential");
System.out.println("created using app context: " + cred2);
String password = ((args.length > 0) ? args[0] : "baz");
cred2.setPassword(password);
System.out.println("initialized using setter: " + cred2);
}
public Credential()
{
this(DEFAULT_USERNAME, DEFAULT_PASSWORD);
}
public Credential(String username, String password)
{
this.setUsername(username);
this.setPassword(password);
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String toString()
{
return new StringBuilder().append("Credential{").append("username='").append(username).append('\'').append(", password='").append(password).append('\'').append('}').toString();
}
}
I created a Decryptor interface:
package aop;
public interface Decryptor
{
String decrypt(String encrypted);
}
And a DecryptorImpl:
package aop;
public class DecryptorImpl implements Decryptor
{
public static final String DEFAULT_DECRYPTED_VALUE = " - not secret anymore";
public String decrypt(String encrypted)
{
// Any transform will do; this suffices to demonstrate
return encrypted + DEFAULT_DECRYPTED_VALUE;
}
}
I needed DecryptorAdvice to implement Spring's MethodBeforeAdvice:
package aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class DecryptionAdvice implements MethodBeforeAdvice
{
private Decryptor decryptor;
public DecryptionAdvice(Decryptor decryptor)
{
this.decryptor = decryptor;
}
public void before(Method method, Object[] args, Object target) throws Throwable
{
String encryptedPassword = (String) args[0];
args[0] = this.decryptor.decrypt(encryptedPassword);
}
}
And I wired it together in an aop-context.xml. (If you tell me how to get XML to display, I'll post it.) Note the passwordDecryptionAdvisor: it only matches the setPassword method.
The interesting part happens when I run it. Here's what I see in the console:
created using new: Credential{username='foo', password='bar'}
created using app context: Credential{username='stackoverflow', password='encrypted-password'}
initialized using setter: Credential{username='stackoverflow', password='baz - not secret anymore'}
What this tells me is:
If I create an object with new it's
not under Spring's control, advice
isn't applied.
If I call setPassword in the ctor
before the app context is
initialized, advice isn't applied.
If I call setPassword in my code
after the app context is
initialized, advice is applied.
I hope this can help you.

I thought you wanted the beforeMethod advice to use the encrypted password String that's passed into the setPassword method. You want to decrypt that and have the advised class get an decrypted version.
I also don't see a proxy interface set in your proxy factory. "Spring In Action" says "...Creating a proxy with interfaces is favored over proxying classes..." Proxying classes should be the exception, not the rule.
Post your advice class.

Related

MongoDB-Escape dots '.' in map key]

Map key codeofproduct contains dots but no replacement was configured! Make sure map keys don't contain dots in the first place or configure an appropriate replacement!
org.springframework.data.mapping.model.MappingException: Map key foo.bar.key contains dots but no replacement was configured! Make sure map keys don't contain dots in the first place or configure an appropriate replacement!
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.potentiallyEscapeMapKey(MappingMongoConverter.java:622)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeMapInternal(MappingMongoConverter.java:586)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.createMap(MappingMongoConverter.java:517)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:424)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:386)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:373)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:257)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:373)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:451)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:386)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:373)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:257)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:373)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:451)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:386)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:373)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:257)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:373)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:345)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.write(MappingMongoConverter.java:310)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.write(MappingMongoConverter.java:77)
at org.springframework.data.mongodb.core.MongoTemplate.doSave(MongoTemplate.java:859)
at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:806)
at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:794)
When we try to insert value, this happens. How can we solve this?
this is my class
#Configuration
#EnableMongoRepositories("net.ooo.hepsiburada.**.repository")
#Profile(Constants.SPRING_PROFILE_CLOUD)
public class CloudMongoDbConfiguration extends AbstractMongoConfiguration {
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
#Inject
private MongoDbFactory mongoDbFactory;
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converterList = new ArrayList<>();;
converterList.add(DateToZonedDateTimeConverter.INSTANCE);
converterList.add(ZonedDateTimeToDateConverter.INSTANCE);
converterList.add(DateToLocalDateConverter.INSTANCE);
converterList.add(LocalDateToDateConverter.INSTANCE);
converterList.add(DateToLocalDateTimeConverter.INSTANCE);
converterList.add(LocalDateTimeToDateConverter.INSTANCE);
return new CustomConversions(converterList);
}
#Override
protected String getDatabaseName() {
return mongoDbFactory.getDb().getName();
}
#Override
public Mongo mongo() throws Exception {
return mongoDbFactory().getDb().getMongo();
}
}
When using Spring Data MongoDB you get an instance of: org.springframework.data.mongodb.core.convert.MappingMongoConverter that has mapKeyDotReplacement set to null by default - that is why you are getting an exception.
You need to either create your own instance of org.springframework.data.mongodb.core.convert.MappingMongoConverter or just modify existing instance using its provider setter method:
/**
* Configure the characters dots potentially contained in a {#link Map} shall be replaced with. By default we don't do
* any translation but rather reject a {#link Map} with keys containing dots causing the conversion for the entire
* object to fail. If further customization of the translation is needed, have a look at
* {#link #potentiallyEscapeMapKey(String)} as well as {#link #potentiallyUnescapeMapKey(String)}.
*
* #param mapKeyDotReplacement the mapKeyDotReplacement to set
*/
public void setMapKeyDotReplacement(String mapKeyDotReplacement) {
this.mapKeyDotReplacement = mapKeyDotReplacement;
}
In MongoDB, dot is always treated as a special character so avoiding it will most likely save you some other headache in the future.
EDIT:
To override default MappingMongoConverter add the following bean declaration:
#Bean
public MappingMongoConverter mongoConverter(MongoDbFactory mongoFactory) throws Exception {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
mongoConverter.setMapKeyDotReplacement(".");
return mongoConverter;
}
My exception:
org.springframework.data.mapping.MappingException: Map key VAT Registration No. contains dots but no replacement was configured! Make sure map keys don't contain dots in the first place or configure an appropriate replacement!
Field with a dot at the end: VAT Registration No.
This didn't work for me:
mongoConverter.setMapKeyDotReplacement(".");
mongoConverter.setMapKeyDotReplacement("_"); //this broke enum values for example VALUE_1 -> VALUE.1
This works for me:
mongoConverter.setMapKeyDotReplacement("-DOT")
Complete class:
#Configuration
public class MongoConfiguration {
#Bean
public MappingMongoConverter mongoConverter(MongoDbFactory mongoFactory, MongoMappingContext mongoMappingContext) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
mongoConverter.setMapKeyDotReplacement("-DOT");
return mongoConverter;
}
}
For XML configuration following will be useful.
Note : mongoConverter bean is used for this. It will replace "." in key with "_"
<bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<mongo:auditing mapping-context-ref="mappingContext"/>
<mongo:db-factory id="mongoDbFactory" mongo-ref="mongoClient" dbname="${mongo.dbname}"/>
<bean id ="mongoConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
<constructor-arg name="mappingContext" ref="mappingContext"/>
<property name="mapKeyDotReplacement" value="_"></property>
</bean>
<mongo:mongo-client id="mongoClient" credentials="${mongo.credential}" >
<mongo:client-options connections-per-host="50" threads-allowed-to-block-for-connection-multiplier="5000" />
</mongo:mongo-client>
<!-- MongoDB Template -->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
<constructor-arg name="mongoConverter" ref="mongoConverter"/>
</bean>

What are the other ways to specify code for Insight to analyze?

Spring Insight documentation states:
A trace represents a thread of execution. It is usually started by an HTTP request but can also be started by a background job
My application architecture style is one of queues running in the background that I'd like to instrument as well. However, I can't figure out how to get Spring Insight to instrument these calls initiated by queued message. I.e. I'd like to instrument the trace after a message is read off of the queue.
How can I ensure Insight instruments these background jobs?
I ended up creating an aspect that targets all of the Command Handlers. It extends the AbstractOperationCollectionAspect, implements the collectionPoint aspect passing in the Handler as an argument to use when it implements the createOperation method.
I.e.
public aspect CommandHandlerOperationCollectionAspect extends AbstractOperationCollectionAspect
{
public pointcut collectionPoint():
execution(* com.xtrac.common.core.handler.ThreadedHandler.HandlerRunnable.executeActorHandler(com.xtrac.common.core.handler.Handler,java.lang.Object));
protected Operation createOperation(JoinPoint jp)
{
Object[] args = jp.getArgs();
com.xtrac.common.core.handler.Handler handler = (Handler) args[0];
Operation operation = new Operation()
.type(XTRACOperationType.COMMAND_HANDLER)
.label(handler.getClass().getSimpleName())
.sourceCodeLocation(getSourceCodeLocation(jp));
return operation;
}
#Override
public String getPluginName()
{
return HandlerPluginRuntimeDescriptor.PLUGIN_NAME;
}
#Override
public boolean isMetricsGenerator()
{
return true;
}
}
I also implemented an AbstractSingleTypeEndpointAnalyzer to fill out the analyzer:
public class HandlerEndPointAnalyzer extends AbstractSingleTypeEndpointAnalyzer
{
private static final HandlerEndPointAnalyzer INSTANCE=new HandlerEndPointAnalyzer();
private HandlerEndPointAnalyzer() {
super(XTRACOperationType.COMMAND_HANDLER);
}
public static final HandlerEndPointAnalyzer getInstance() {
return INSTANCE;
}
#Override
protected EndPointAnalysis makeEndPoint(Frame handlerFrame, int depth) {
Operation operation = handlerFrame.getOperation();
String resourceLabel = operation.getLabel();
String exampleRequest = EndPointAnalysis.getHttpExampleRequest(handlerFrame);
return new EndPointAnalysis(EndPointName.valueOf(resourceLabel),
resourceLabel,
exampleRequest,
getOperationScore(operation, depth),
operation);
}
being sure to add it as a descriptor:
public class HandlerPluginRuntimeDescriptor extends PluginRuntimeDescriptor {
public static final String PLUGIN_NAME = "handler";
private static final HandlerPluginRuntimeDescriptor INSTANCE=new HandlerPluginRuntimeDescriptor();
private static final List<? extends EndPointAnalyzer> epAnalyzers=
ArrayUtil.asUnmodifiableList(HandlerEndPointAnalyzer.getInstance());
private HandlerPluginRuntimeDescriptor() {
super();
}
public static final HandlerPluginRuntimeDescriptor getInstance() {
return INSTANCE;
}
#Override
public Collection<? extends EndPointAnalyzer> getEndPointAnalyzers() {
return epAnalyzers;
}
#Override
public String getPluginName() {
return PLUGIN_NAME;
}
}
All noted in the spring xml file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:insight="http://www.springframework.org/schema/insight-idk"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/insight-idk http://www.springframework.org/schema/insight-idk/insight-idk-1.0.xsd">
<insight:plugin name="handler" version="${project.version}" publisher="XTRAC Solutions LLC" />
<insight:operation-group group="XTRAC Handlers" operation="command_handler_operation" />
<insight:operation-group group="XTRAC Handlers" operation="event_handler_operation" />
<insight:operation-group group="XTRAC Classic" operation="xtrac_workflow_operation" />
<insight:operation-view operation="command_handler_operation"
template="com/xtrac/insight/command_handler_operation.ftl" />
<insight:operation-view operation="event_handler_operation"
template="com/xtrac/insight/event_handler_operation.ftl" />
<insight:operation-view operation="xtrac_workflow_operation"
template="com/xtrac/insight/xtrac_workflow_operation.ftl" />
<bean id="handlerPluginEndPointAnalyzer"
class="com.xtrac.insight.HandlerEndPointAnalyzer"
factory-method="getInstance"
lazy-init="true"
/>
<bean id="handlerPluginRuntimeDescriptor"
class="com.xtrac.insight.HandlerPluginRuntimeDescriptor"
factory-method="getInstance"
lazy-init="true"
/>
</beans>
along with some ftls.
I also created a MethodOperationCollectionAspect to collect some of the web service calls that occur in these handers. This sets it up for a nice display that tells me a lot about what is going on during the hander operation, and how much time it takes. E.g.
This set up a framework for maintaining a monitor on the health of the application if I set up the base line Thresholds for the named handlers
This is very useful because I can then tell if the application is healthy. Otherwise, the endpoints default to <200 ms for healthy.

Spring Environment Property Source Configuration

I'm working on an application library with a utility class called "Config" which is backed by the Spring Environment object and provides strongly typed getters for all the applications configuration values.
The property sources for the configuration can vary depending on environment (DEV/PROD) and usage (standalone/test/webapp), and can range from the default ones (system & env props) to custom database and JNDI sources.
What I'm struggling with is how to let the apps consuming this library easily configure the property source(s) used by Environment, such that the properties are available for use in our Config class and via the PropertySourcesPlaceholderConfigurer.
We're still using XML configuration, so ideally this could be configured in XML something like.
<bean id="propertySources" class="...">
<property name="sources">
<list>
<ref local="jndiPropertySource"/>
<ref local="databasePropertySource"/>
</list>
</property>
</bean>
...and then injected somehow into the Environment's property sources collection.
I've read that something like this may not be possible due to the timing of the app context lifecycle, and that this may need to be done using an application initializer class.
Any ideas?
It depends on how you want to use the properties, if it is to inject the properties using ${propertyname} syntax, then yes just having PropertySourcesPlaceHolderConfigurer will work, which internally has access to the PropertySources registered in the environment.
If you plan to use Environment directly, using say env.getProperty(), then you are right - the properties using PropertySourcesPlaceHolderConfigurer are not visible here. The only way then is to inject it using Java code, there are two ways that I know of:
a. Using Java Config:
#Configuration
#PropertySource("classpath:/app.properties")
public class SpringConfig{
}
b. Using a custom ApplicationContextInitializer, the way it is described here
I came up with the following which seems to work, but I'm fairly new to Spring, so I'm not so sure how it will hold up under different use cases.
Basically, the approach is to extend PropertySourcesPlaceholderConfigurer and add a setter to allow the user to easily configure a List of PropertySource objects in XML. After creation, the property sources are copied to the current Environment.
This basically allows the property sources to be configured in one place, but used by both placholder configuration and Environment.getProperty scenarios.
Extended PropertySourcesPlaceholderConfigurer
public class ConfigSourcesConfigurer
extends PropertySourcesPlaceholderConfigurer
implements EnvironmentAware, InitializingBean {
private Environment environment;
private List<PropertySource> sourceList;
// Allow setting property sources as a List for easier XML configuration
public void setPropertySources(List<PropertySource> propertySources) {
this.sourceList = propertySources;
MutablePropertySources sources = new MutablePropertySources();
copyListToPropertySources(this.sourceList, sources);
super.setPropertySources(sources);
}
#Override
public void setEnvironment(Environment environment) {
// save off Environment for later use
this.environment = environment;
super.setEnvironment(environment);
}
#Override
public void afterPropertiesSet() throws Exception {
// Copy property sources to Environment
MutablePropertySources envPropSources = ((ConfigurableEnvironment)environment).getPropertySources();
copyListToPropertySources(this.sourceList, envPropSources);
}
private void copyListToPropertySources(List<PropertySource> list, MutablePropertySources sources) {
// iterate in reverse order to insure ordering in property sources object
for(int i = list.size() - 1; i >= 0; i--) {
sources.addFirst(list.get(i));
}
}
}
beans.xml file showing basic configuration
<beans>
<context:annotation-config/>
<context:component-scan base-package="com.mycompany" />
<bean class="com.mycompany.ConfigSourcesConfigurer">
<property name="propertySources">
<list>
<bean class="org.mycompany.CustomPropertySource" />
<bean class="org.springframework.core.io.support.ResourcePropertySource">
<constructor-arg value="classpath:default-config.properties" />
</bean>
</list>
</property>
</bean>
<bean class="com.mycompany.TestBean">
<property name="stringValue" value="${placeholder}" />
</bean>
</beans>
The following worked for me with Spring 3.2.4 .
PropertySourcesPlaceholderConfigurer must be registered statically in order to process the placeholders.
The custom property source is registered in the init method and as the default property sources are already registered, it can itself be parameterized using placeholders.
JavaConfig class:
#Configuration
#PropertySource("classpath:propertiesTest2.properties")
public class TestConfig {
#Autowired
private ConfigurableEnvironment env;
#Value("${param:NOVALUE}")
private String param;
#PostConstruct
public void init() {
env.getPropertySources().addFirst(new CustomPropertySource(param));
}
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public TestBean1 testBean1() {
return new TestBean1();
}
}
Custom property source:
public class CustomPropertySource extends PropertySource<Object> {
public CustomPropertySource(String param) {
super("custom");
System.out.println("Custom property source initialized with param " + param + ".");
}
#Override
public Object getProperty(String name) {
return "IT WORKS";
}
}
Test bean (getValue() will output "IT WORKS"):
public class TestBean1 {
#Value("${value:NOVALUE}")
private String value;
public String getValue() {
return value;
}
}
I had a similar problem, in my case I'm using Spring in a standalone application, after load the default configurations I may need apply another properties file (lazy load configs) present in a config directory. My solution was inspired this Spring Boot documentation, but with no dependency of Spring Boot. See below the source code:
#PropertySources(#PropertySource(value = "classpath:myapp-default.properties"))
public class PersistenceConfiguration {
private final Logger log = LoggerFactory.getLogger(getClass());
private ConfigurableEnvironment env;
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurerDev(ConfigurableEnvironment env) {
return new PropertySourcesPlaceholderConfigurer();
}
#Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
for(String profile: env.getActiveProfiles()) {
final String fileName = "myapp-" + profile + ".properties";
final Resource resource = new ClassPathResource(fileName);
if (resource.exists()) {
try {
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource(fileName,PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
this.env = env;
}
...
}
I recently ran into the issue of how to register custom property sources in the environment. My specific problem is that I have a library with a Spring configuration that I want to be imported into the Spring application context, and it requires custom property sources. However, I don't necessarily have control over all of the places where the application context is created. Because of this, I do not want to use the recommended mechanisms of ApplicationContextInitializer or register-before-refresh in order to register the custom property sources.
What I found really frustrating is that using the old PropertyPlaceholderConfigurer, it was easy to subclass and customize the configurers completely within the Spring configuration. In contrast, to customize property sources, we are told that we have to do it not in the Spring configuration itself, but before the application context is initialized.
After some research and trial and error, I discovered that it is possible to register custom property sources from inside of the Spring configuration, but you have to be careful how you do it. The sources need to be registered before any PropertySourcesPlaceholderConfigurers execute in the context. You can do this by making the source registration a BeanFactoryPostProcessor with PriorityOrdered and an order that is higher precedence than the PropertySourcesPlaceholderConfigurer that uses the sources.
I wrote this class, which does the job:
package example;
import java.io.IOException;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.PropertiesLoaderSupport;
/**
* This is an abstract base class that can be extended by any class that wishes
* to become a custom property source in the Spring context.
* <p>
* This extends from the standard Spring class PropertiesLoaderSupport, which
* contains properties that specify property resource locations, plus methods
* for loading properties from specified resources. These are all available to
* be used from the Spring configuration, and by subclasses of this class.
* <p>
* This also implements a number of Spring flag interfaces, all of which are
* required to maneuver instances of this class into a position where they can
* register their property sources BEFORE PropertySourcesPlaceholderConfigurer
* executes to substitute variables in the Spring configuration:
* <ul>
* <li>BeanFactoryPostProcessor - Guarantees that this bean will be instantiated
* before other beans in the context. It also puts it in the same phase as
* PropertySourcesPlaceholderConfigurer, which is also a BFPP. The
* postProcessBeanFactory method is used to register the property source.</li>
* <li>PriorityOrdered - Allows the bean priority to be specified relative to
* PropertySourcesPlaceholderConfigurer so that this bean can be executed first.
* </li>
* <li>ApplicationContextAware - Provides access to the application context and
* its environment so that the created property source can be registered.</li>
* </ul>
* <p>
* The Spring configuration for subclasses should contain the following
* properties:
* <ul>
* <li>propertySourceName - The name of the property source this will register.</li>
* <li>location(s) - The location from which properties will be loaded.</li>
* <li>addBeforeSourceName (optional) - If specified, the resulting property
* source will be added before the given property source name, and will
* therefore take precedence.</li>
* <li>order (optional) - The order in which this source should be executed
* relative to other BeanFactoryPostProcessors. This should be used in
* conjunction with addBeforeName so that if property source factory "psfa"
* needs to register its property source before the one from "psfb", "psfa"
* executes AFTER "psfb".
* </ul>
*
* #author rjsmith2
*
*/
public abstract class AbstractPropertySourceFactory extends
PropertiesLoaderSupport implements ApplicationContextAware,
PriorityOrdered, BeanFactoryPostProcessor {
// Default order will be barely higher than the default for
// PropertySourcesPlaceholderConfigurer.
private int order = Ordered.LOWEST_PRECEDENCE - 1;
private String propertySourceName;
private String addBeforeSourceName;
private ApplicationContext applicationContext;
private MutablePropertySources getPropertySources() {
final Environment env = applicationContext.getEnvironment();
if (!(env instanceof ConfigurableEnvironment)) {
throw new IllegalStateException(
"Cannot get environment for Spring application context");
}
return ((ConfigurableEnvironment) env).getPropertySources();
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getPropertySourceName() {
return propertySourceName;
}
public void setPropertySourceName(String propertySourceName) {
this.propertySourceName = propertySourceName;
}
public String getAddBeforeSourceName() {
return addBeforeSourceName;
}
public void setAddBeforeSourceName(String addBeforeSourceName) {
this.addBeforeSourceName = addBeforeSourceName;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Subclasses can override this method to perform adjustments on the
* properties after they are read.
* <p>
* This should be done by getting, adding, removing, and updating properties
* as needed.
*
* #param props
* properties to adjust
*/
protected void convertProperties(Properties props) {
// Override in subclass to perform conversions.
}
/**
* Creates a property source from the specified locations.
*
* #return PropertiesPropertySource instance containing the read properties
* #throws IOException
* if properties cannot be read
*/
protected PropertySource<?> createPropertySource() throws IOException {
if (propertySourceName == null) {
throw new IllegalStateException("No property source name specified");
}
// Load the properties file (or files) from specified locations.
final Properties props = new Properties();
loadProperties(props);
// Convert properties as required.
convertProperties(props);
// Convert to property source.
final PropertiesPropertySource source = new PropertiesPropertySource(
propertySourceName, props);
return source;
}
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// Create the property source, and get its desired position in
// the list of sources.
if (logger.isDebugEnabled()) {
logger.debug("Creating property source [" + propertySourceName
+ "]");
}
final PropertySource<?> source = createPropertySource();
// Register the property source.
final MutablePropertySources sources = getPropertySources();
if (addBeforeSourceName != null) {
if (sources.contains(addBeforeSourceName)) {
if (logger.isDebugEnabled()) {
logger.debug("Adding property source ["
+ propertySourceName + "] before ["
+ addBeforeSourceName + "]");
}
sources.addBefore(addBeforeSourceName, source);
} else {
logger.warn("Property source [" + propertySourceName
+ "] cannot be added before non-existent source ["
+ addBeforeSourceName + "] - adding at the end");
sources.addLast(source);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Adding property source ["
+ propertySourceName + "] at the end");
}
sources.addLast(source);
}
} catch (Exception e) {
throw new BeanInitializationException(
"Failed to register property source", e);
}
}
}
Of note here is that the default order of this property source factory class is higher precedence than the default order of PropertySourcesPlaceholderConfigurer.
Also, the registration of the property source happens in postProcessBeanFactory, which means that it will execute in the correct order relative to the PropertySourcesPlaceholderConfigurer. I discovered the hard way that InitializingBean and afterPropertiesSet do not respect the order parameter, and I gave up on that approach as being wrong and redundant.
Finally, because this is a BeanFactoryPostProcessor, it is a bad idea to try to wire much in the way of dependencies. Therefore, the class accesses the environment directly through the application context, which it obtains using ApplicationContextAware.
In my case, I needed the property source to decrypt password properties, which I implemented using the following subclass:
package example;
import java.util.Properties;
/**
* This is a property source factory that creates a property source that can
* process properties for substituting into a Spring configuration.
* <p>
* The only thing that distinguishes this from a normal Spring property source
* is that it decrypts encrypted passwords.
*
* #author rjsmith2
*
*/
public class PasswordPropertySourceFactory extends
AbstractPropertySourceFactory {
private static final PasswordHelper passwordHelper = new PasswordHelper();
private String[] passwordProperties;
public String[] getPasswordProperties() {
return passwordProperties;
}
public void setPasswordProperties(String[] passwordProperties) {
this.passwordProperties = passwordProperties;
}
public void setPasswordProperty(String passwordProperty) {
this.passwordProperties = new String[] { passwordProperty };
}
#Override
protected void convertProperties(Properties props) {
// Adjust password fields by decrypting them.
if (passwordProperties != null) {
for (String propName : passwordProperties) {
final String propValue = props.getProperty(propName);
if (propValue != null) {
final String plaintext = passwordHelper
.decryptString(propValue);
props.setProperty(propName, plaintext);
}
}
}
}
}
Finally, I specifed the property source factory in my Spring configuration:
<!-- Enable property resolution via PropertySourcesPlaceholderConfigurer.
The order has to be larger than the ones used by custom property sources
so that those property sources are registered before any placeholders
are substituted. -->
<context:property-placeholder order="1000" ignore-unresolvable="true" />
<!-- Register a custom property source that reads DB properties, and
decrypts the database password. -->
<bean class="example.PasswordPropertySourceFactory">
<property name="propertySourceName" value="DBPropertySource" />
<property name="location" value="classpath:db.properties" />
<property name="passwordProperty" value="db.password" />
<property name="ignoreResourceNotFound" value="true" />
<!-- Order must be lower than on property-placeholder element. -->
<property name="order" value="100" />
</bean>
To be honest, with the defaults for order in PropertySourcesPlaceholderConfigurer and AbstractPropertySourceFactory, it is probably not even necessary to specify order in the Spring configuration.
Nonetheless, this works, and it does not require any fiddling with the application context initialization.

SimpleMailMessage is not working

im using spring SimpleMailMessage,whenever i try to send mail function i get this exception:
java.lang.IllegalArgumentException: The 'original' message argument cannot be null
My spring.xml
<bean id="MailSender" class="com.util.MailSender">
<property name="mailSender" ref="mailSender"/>
<property name="templateMessage" ref="templateMessage"/>
</bean>
MailSenderClass:
private MailSender mailSender;
private SimpleMailMessage templateMessage;
//getter and setter
public void sendMail(String content, List<String> listEmail){
SimpleMailMessage[] mailMessageArray = new SimpleMailMessage[listEmail.size()];
Iterator<String> iterator = listEmail.iterator();
for (int index = 0; iterator.hasNext(); index ++){
SimpleMailMessage message = new SimpleMailMessage(this.templateMessage);
String toAddress = iterator.next();
message.setTo(toAddress);
message.setText(content);
mailMessageArray[index] = message;
}
this.mailSender.send(mailMessageArray);
}
Action Bean Class :
private MailSender mailSender=new MailSender();
public void ReSend(){
mailSender.sendMail(listBEeans[i].getEmailContent(),listEmail);
//able to pass the content and listEmails correctly
}
Did i miss any configurations in my action bean? Thank you
It looks like your bean is creating a local reference to MailSender instead of using the injected dependency (assuming you created one). Somewhere you should have:
AnObject
{
...
MailSender mailsender = (MailSender)context.getBean("mailSender");
...
}
And your bean should then say:
private MailSender = AnObject.getMailSender();
The problem is your template used in the constructor is null. It cannot be.
From the code of SimpleMailMessage:
public SimpleMailMessage(SimpleMailMessage original) {
Assert.notNull(original, "The 'original' message argument cannot be null");
...
Check if your bean templateMessage is really set to something or remove it from the MailSender and use the no-args constructor.

Custom property editors do not work for request parameters in Spring MVC?

I'm trying to create a multiaction web controller using Spring annotations. This controller will be responsible for adding and removing user profiles and preparing reference data for the jsp page.
#Controller
public class ManageProfilesController {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(UserAccount.class,"account", new UserAccountPropertyEditor(userManager));
binder.registerCustomEditor(Profile.class, "profile", new ProfilePropertyEditor(profileManager));
logger.info("Editors registered");
}
#RequestMapping("remove")
public void up( #RequestParam("account") UserAccount account,
#RequestParam("profile") Profile profile) {
...
}
#RequestMapping("")
public ModelAndView defaultView(#RequestParam("account") UserAccount account) {
logger.info("Default view handling");
ModelAndView mav = new ModelAndView();
logger.info(account.getLogin());
mav.addObject("account", account);
mav.addObject("profiles", profileManager.getProfiles());
mav.setViewName(view);
return mav;
}
...
}
Here is the part of my webContext.xml file:
<context:component-scan base-package="ru.mirea.rea.webapp.controllers" />
<context:annotation-config/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
...
/home/users/manageProfiles=users.manageProfilesController
</value>
</property>
</bean>
<bean id="users.manageProfilesController" class="ru.mirea.rea.webapp.controllers.users.ManageProfilesController">
<property name="view" value="home\users\manageProfiles"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
However, when i open the mapped url, i get exception:
java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [ru.mirea.rea.model.UserAccount]: no matching editors or conversion strategy found
I use spring 2.5.6 and plan to move to the Spring 3.0 in some not very distant future. However, according to this JIRA https://jira.springsource.org/browse/SPR-4182 it should be possible already in spring 2.5.1.
The debug shows that the InitBinder method is correctly called.
What am i doing wrong?
Update:
public class UserAccountPropertyEditor extends PropertyEditorSupport {
static Logger logger = Logger.getLogger(UserAccountPropertyEditor.class);
public UserAccountPropertyEditor(IUserDAO dbUserManager) {
this.dbUserManager = dbUserManager;
}
private IUserDAO dbUserManager;
public String getAsText() {
UserAccount obj = (UserAccount) getValue();
if (null==obj) {
return "";
} else {
return obj.getId().toString();
}
}
public void setAsText(final String value) {
try {
Long id = Long.parseLong(value);
UserAccount acct = dbUserManager.getUserAccountById(id);
if (null!=acct) {
super.setValue(acct);
} else {
logger.error("Binding error. Cannot find userAccount with id ["+value+"]");
throw new IllegalArgumentException("Binding error. Cannot find userAccount with id ["+value+"]");
}
} catch (NumberFormatException e) {
logger.error("Binding error. Invalid id: " + value);
throw new IllegalArgumentException("Binding error. Invalid id: " + value);
}
}
}
There are no errors logged from UserAccountPropertyEditor.
I don't think you want to be specifying the field argument to WebDataBinder.registerCustomEditor(). This intended to work alongside form-backing objects, and you're not using that.
Try the simpler 2-arg method instead, and it should work:
binder.registerCustomEditor(UserAccount.class, new UserAccountPropertyEditor(userManager));
binder.registerCustomEditor(Profile.class, new ProfilePropertyEditor(profileManager));

Resources