View Full Version : conditional processing
bryanhunt
Aug 30th, 2004, 10:51 AM
Hi there,
This is a feature that I don't think ( after reading the documentation )
spring currently supports but I think it would be a cool feature so here
it is.
I am using the spring PreferencesPlaceholderConfigurer bean and
storing my preferences in the registry. This works absolutely great.
I can ship my web application and the user can run a simple
command line config tool which can configure their database etc
without them having to mess arround with tomcat config files etc.
This I think is super cool !
However I think there is something else really usefull that you could
do with this.
If it were possible to
a) Include or exclude a section of the spring configuration file based on
a property it would make setup and testing a snip.
Here is what I mean
<conditional property="{jestate.testing}" value="true">
<bean id="authenticationBean" class="ie.jestate.testing.AuthenticationBean"/>
</conditional>
<conditional property="{jestate.testing}" value="false">
<bean id="authenticationBean" class="ie.jestate.AuthenticationBean"/>
</conditional>
and or
b) Include files within the tree.
<conditional property="{jestate.testing}" value="true">
<loadbeandef="classpath:com/mycompany/mypackage/test-datasource.xml"/>
</conditional>
<conditional property="{jestate.testing}" value="false">
<loadbeandef="classpath:com/mycompany/mypackage/datasource.xml"/>
</conditional>
Maybee this stuff is too complicated to implement, I don't know as
I amn't a real xml expert. But it strikes me that this would be a
really cool feature.
It is very popular allready with ANT
--b
bryanhunt
Aug 30th, 2004, 11:30 AM
This would mean that you could have one set of spring configuration files
for your entire application.
Just setting the testing property would enable your testing or live
configuration.
You would no longer have to maintain seperate config files for web and
multiple testing files for different configurations.
--b
bryanhunt
Aug 30th, 2004, 12:14 PM
Perhaps
org.springframework.context.support.ClassPathXmlAp plicationContext
Could be subclassed somehow to do this ?
Config would then be like so
<beans>
<bean id="examples.spring" class="org.springframework.context.support.ConfigurableCl assPathXmlApplicationContext">
<constructor-arg>
<map>
<entry key="jestate.testing=true">
<value>examples/spring/spring.xml</value>
</entry>
<entry key="jestate.testing=false">
<value>examples/spring/authentication.xml</value>
</entry>
<entry key="jestate.testing=false">
<value>examples/spring/spring-database.xml</value>
</entry>
</map>
</constructor-arg>
<constructor-arg>
<map>
<entry key="jestate.testing">
<value>${jestate.testing}</value>
</entry>
</map>
</constructor-arg>
</bean>
</beans>
Is this a good idea ? If you are interested I can create this and submit my
code to the project.
--b
mperham
Aug 30th, 2004, 12:15 PM
But spring already supports this in a much cleaner method (IMO) without conditionals by just specifying the context files you want to use:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"applicationContext-domain.xml",
"applicationContext-domain-test.xml" });
Where domain is my live configuration and test overrides certain values in it to use a Hypersonic VM db instead. The override capability is why you don't need conditionals.
This would mean that you could have one set of spring configuration files
for your entire application.
Just setting the testing property would enable your testing or live
configuration.
You would no longer have to maintain seperate config files for web and
multiple testing files for different configurations.
bryanhunt
Aug 30th, 2004, 12:23 PM
Yes but I may want to run the web application in testing mode.
ie without enabling authentication or enabling default auth so that
any username/password combination will work.
I could do it the way that you suggest but it would mean that I
would have to access the preferences api in my unit tests and
in my struts actions.
It would be cooler if all the preferences based config was in
the spring configuration.
For example here is a snippet from my current config.
<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.Preferenc esPlaceholderConfigurer">
<property name="userTreePath"><value>ie/jestate</value></property>
</bean></programlisting>
Here is the configuration of the data source further on in the applicationContext file ...
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>${jestate.driver.class.name}</value>
</property>
<property name="url">
<value>${jestate.database.url}</value>
</property>
<property name="username">
<value>${jestate.username}</value>
</property>
<property name="password">
<value>${jestate.password}</value>
</property>
</bean>
Now I am going to have a lot of things in my spring config.
I'm running it with ageci so I'm going to have AOP method
interception going on.
I'm planning on shipping a httpinvoker SWT webstarted
application as well.
I can see a situation where I start to get a lot of spring
configuration files.
If I could process differently based on a simple couple of
preferences based variables it would make my project
a lot neater and simpler for people to use and get started
as developers with it.
karsten
Aug 30th, 2004, 12:28 PM
or you take ant to do this.
ant builds your application-context.xml into the build folder from a xml file looking similar to the one you mentioned. Ant is checking the value based on what you specify in a build.properties or similar, and after that either excludes the bean or inserts it into the builded xml file.
Hth.
bryanhunt
Aug 30th, 2004, 12:34 PM
or you take ant to do this.
ant builds your application-context.xml into the build folder from a xml file looking similar to the one you mentioned. Ant is checking the value based on what you specify in a build.properties or similar, and after that either excludes the bean or inserts it into the builded xml file.
Hth.
I am looking to be able to do the following.
Provide my application http://jestate.dev.java.net as a war file.
Provide a simple command line application ( already made )
which can be used to configure it.
The user
a) Runs the config program ( which is perhaps run as part of the
installer )
b) Makes sure a suitable database exists on their system and
they have access rights to it.
c) Drops the war file into their tomcat directory.
d) Scoffs at the next person who says open source is hard to
configure and set up.
I don't want the average user to have to install ANT/download different
versions etc
The easier i make this to deploy the more people who are going to be
able to easily run this.
memelet
Aug 30th, 2004, 01:00 PM
I have approached this a little differently.
When running our integration/system tests, there are just a few components that need to be replaced with simulators. These components are defined in the production appcontext, and "overridden" in an integration-test appcontext, eg:
Production
<bean id="com.its.marketdata.realtimeCurrentPriceService"
class="com.its.marketdata.service.currentprice.RealTimeCu rrentPriceServiceFactoryBean" lazy-init="true">
</bean>
<bean id="com.its.marketdata.delayedCurrentPriceService"
class="com.its.marketdata.service.currentprice.DelayedCur rentPriceServiceFactoryBean" lazy-init="true">
</bean>
Integration Test
<bean id="com.its.marketdata.simulatedCurrentPriceService"
class="test.ftest.trading.SimulatedCurrentPriceServiceFac toryBean">
</bean>
<bean id="ftest.beanOverrides"
class="test.ftest.trading.OverrideForTestBeanFactoryPostP rocessor">
<property name="overrides">
<map>
<entry key="com.its.marketdata.realtimeCurrentPriceService">
<value>com.its.marketdata.simulatedCurrentPriceService</value>
</entry>
<entry key="com.its.marketdata.delayedCurrentPriceService">
<value>com.its.marketdata.simulatedCurrentPriceService</value>
</entry>
</map>
</property>
</bean>
The class OverrideForTestBeanFactoryPostProcessor handles swapping out the production AbstractBeanDefinition for the test version:
public class OverrideForTestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private Map overrides = new HashMap();
public void setOverrides(Map overrides) {
this.overrides = overrides;
}
public void postProcessBeanFactory(ConfigurableListableBeanFac tory beanFactory) throws BeansException {
for (Iterator beanNameIterator = overrides.keySet().iterator(); beanNameIterator.hasNext();) {
String beanName = (String)beanNameIterator.next();
String overrideBeanName = (String)overrides.get(beanName);
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)beanFactory.getBeanDefinit ion(beanName);
AbstractBeanDefinition overrideBeanDefinition = (AbstractBeanDefinition)beanFactory.getBeanDefinit ion(overrideBeanName);
beanDefinition.overrideFrom(overrideBeanDefinition );
}
}
}
mperham
Aug 30th, 2004, 01:59 PM
Barry, why wouldn't your integration context just look like this? Seems like you are making it harder than it needs to be?
<bean id="com.its.marketdata.realtimeCurrentPriceService"
class="test.ftest.trading.SimulatedCurrentPriceServiceFac toryBean" lazy-init="true">
</bean>
<bean id="com.its.marketdata.delayedCurrentPriceService"
class="test.ftest.trading.SimulatedCurrentPriceServiceFac toryBean" lazy-init="true">
</bean>
memelet
Aug 30th, 2004, 07:09 PM
Barry, why wouldn't your integration context just look like this?
No, because I am including the entire production-context.xml into the appcontext along with the integration-context.xml and doing what you showed would be defining two beans of the same id. (Or is allowed to override an existing bean id?)
If there were some form of conditional processing (as the orignal post suggested) then what I did would not be necessary. But I'm not sure I would want #ifs in my production context.xml.
mperham
Aug 30th, 2004, 10:01 PM
Overriding is allowed. That's the whole point - your test context just overrides the few beans which need to change for your unit tests. 90% of your context stays the same and you don't have to maintain two separate copies of the same context.
No, because I am including the entire production-context.xml into the appcontext along with the integration-context.xml and doing what you showed would be defining two beans of the same id. (Or is allowed to override an existing bean id?)
bryanhunt
Aug 31st, 2004, 04:48 AM
Hello all,
I am sure there is a neater or better way to implement this but it is
working for me. There is no testcase but I have tested this using the
"main" method.
This code is free for anyone to use and I would be delighted if it was
included in spring.
/*
* Created on 30 août 2004
*/
package ie.jestate.temp.spring;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.context.support.AbstractXmlApp licationContext;
/**
* @author Administrateur
*
*/
public class ConditionalClassPathXMLApplicationContext
extends AbstractXmlApplicationContext {
public static void main(String[] args) {
Map mappings = new HashMap();
mappings.put("jestate.testing=true","classpath:test/ie/jestate/applicationContext-hibernate.xml");
mappings.put("jestate.db=true","classpath:test/ie/jestate/applicationContext-hibernate.xml");
mappings.put("jestate.anothervar=true","classpath:test/ie/jestate/anothervar.xml");
Map variables = new HashMap();
variables.put("jestate.testing","true");
variables.put("jestate.db","false");
variables.put("jestate.anothervar","false");
ConditionalClassPathXMLApplicationContext test = new ConditionalClassPathXMLApplicationContext(mappings ,variables);
}
private String[] configLocations ;
public ConditionalClassPathXMLApplicationContext(Map mappings, Map variables)
throws BeansException {
Set mappingKeys = mappings.keySet();
Set variableKeys = variables.keySet();
Set configLocationSet = new HashSet();
Iterator variableKeysIterator = variableKeys.iterator();
while (variableKeysIterator.hasNext()) {
String variableKey = (String ) variableKeysIterator.next();
String mapping = (String) variables.get(variableKey);
String mappingKey = variableKey + "=" + mapping;
logger.debug("mappingKey='" + mappingKey + "'");
logger.debug("mappings.get(mappingKey)='" + mappings.get(mappingKey) + "'");
if(mappings.get(mappingKey) != null) {
configLocationSet.add(mappings.get(mappingKey));
}
}
configLocations = new String[configLocationSet.size()];
Iterator iterator = configLocationSet.iterator();
configLocations = (String[]) configLocationSet.toArray(configLocations);
for (int ii = 0; ii < configLocations.length; ii++) {
logger.debug(configLocations[ii]);
}
refresh();
}
/* (non-Javadoc)
* @see org.springframework.context.support.AbstractXmlApp licationContext#getConfigLocations()
*/
protected String[] getConfigLocations() {
return configLocations;
}
}
memelet
Aug 31st, 2004, 08:30 AM
Overriding is allowed. That's the whole point - your test context just overrides the few beans which need to change for your unit tests. 90% of your context stays the same and you don't have to maintain two separate copies of the same context.
Cool! Thanks.
bryanhunt
Aug 31st, 2004, 09:40 AM
But spring already supports this in a much cleaner method (IMO) without conditionals by just specifying the context files you want to use:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"applicationContext-domain.xml",
"applicationContext-domain-test.xml" });
Where domain is my live configuration and test overrides certain values in it to use a Hypersonic VM db instead. The override capability is why you don't need conditionals.
This would mean that you could have one set of spring configuration files
for your entire application.
Just setting the testing property would enable your testing or live
configuration.
You would no longer have to maintain seperate config files for web and
multiple testing files for different configurations.
The problem with that is that if you were using the contextLoader Servlet,
contextLoaderListener or HessianServiceExporter or HttpInvokerExporter
you would need to subclass them and add your own conditional logic
to load appropriate configuration files.
With my approach you don't need to do this. You have one set of files
for everything ie testing/live/with transactions/without transactions/
with method interception from ageci/without method interception from ageci and it all works the same based on either
a) A properties file
or
b) A preference set in the system registry.
mperham
Aug 31st, 2004, 08:44 PM
The problem with that is that if you were using the contextLoader Servlet, contextLoaderListener or HessianServiceExporter or HttpInvokerExporter you would need to subclass them and add your own conditional logic to load appropriate configuration files.
But I don't have a single line of conditional logic. And I think that is the common case.
With my approach you don't need to do this. You have one set of files for everything ie testing/live/with transactions/without transactions/with method interception from ageci/without method interception from ageci and it all works the same based on either a) A properties file or b) A preference set in the system registry.
Why on earth would you want to support all those configuration permutations? I have a production configuration which I have settled on. My unit tests mock that configuration as closely as possible without any conditional logic. I am extremely pleased with how "right" the Spring context design feels. Adding conditional logic to the design seems like it is a recipe for allowing people to do the wrong thing.
bryanhunt
Sep 1st, 2004, 09:00 AM
The problem with that is that if you were using the contextLoader Servlet, contextLoaderListener or HessianServiceExporter or HttpInvokerExporter you would need to subclass them and add your own conditional logic to load appropriate configuration files.
But I don't have a single line of conditional logic. And I think that is the common case.
With my approach you don't need to do this. You have one set of files for everything ie testing/live/with transactions/without transactions/with method interception from ageci/without method interception from ageci and it all works the same based on either a) A properties file or b) A preference set in the system registry.
Why on earth would you want to support all those configuration permutations? I have a production configuration which I have settled on. My unit tests mock that configuration as closely as possible without any conditional logic. I am extremely pleased with how "right" the Spring context design feels. Adding conditional logic to the design seems like it is a recipe for allowing people to do the wrong thing.
Probably because you have a simple system.
Here are (some ) of the configurations I need to support
1 with remoting support and acegi security support
2 with remoting support and without acegi security
3 without remoting support
4 client mode ie only a desktop application
3 setup mode ( to add core users,areas to the database )
4 unit testing of services
5 unit testing of services using mock DAO objects
6 unit testing of services with real DAO objects
etc etc etc etc ......
So you may have some idea why I need something where
I can set the configuration using the preferences API
Also I want the *User/Administrator* to be able to change these
settings. I do not want them to have to have ANT installed,
I do not want them to have to know what a properties file is
I do not want them to have to know how to edit a properties
file.
I want a flexible system where i dont have to *** HARD CODE ***
the different configuration options into my components.
What meets your needs is not necessarily the same thing that
meets MY needs or my USERS needs.
There is no one size fits all solution and that is why i am trying to
do this and if it is usefull to others well then that is a good thing.
--b
jasonchen_nj
Sep 8th, 2004, 01:14 PM
Based on my own experience, it's 100% needed. I have to create my own singleton (non-spring) named as IocContextManager to load the context instead of depending on the web.xml.
In an enterprise environment, we have to implement a lot of features and make them “backward” compatible. For example, swapping out the JDBC and replacing with WebSphere data source, swapping out MQ connection with JCA connectors’ implementation, etc. In order to make it work, I have to create two context files using the same interface but swap the files during the run-time. So if anything goes wrong at the very end of the project cycle, a “fall-back” is still possible to be as a last resort.
As the coding-test-build-test-deploy kind of project life cycle going on, we can’t modify the web.xml to remove a context definition at the end. However, changing a property flag (test condition) is acceptable from the project manager’s point of view as long as both the implementations have been tested. Also, changing a conditional switch is so much easier to deal with than telling our deployer to figure out which context file(s) to be removed from the web.xml.
It’s also much easier to test because the Spring Context will be reloaded when my singleton destroyed (but not the web.xml).
That’s the reality.
bryanhunt
Sep 9th, 2004, 07:14 AM
It's wierd the way that anytime you suggest anything in life there is
a certain number of people who say .. no it should never be done like
that ... it wont work etc ....
I have two virtually identical spring config files.
I'm using acegi ,security interception, transactions, hibernate etc etc
The two files are nearly identical but I have to maintain two parallel
files and shift changes from one to the other constantly.
I think a simple "ifdef" conditional processing mechanism would
simplify things rather than make them more complicated.
I also wonder what stage in their project a lot of people that replied
were at because this is the kinda thing that starts to stress you as
you get close to deployment stage.
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.