We provide a ParticipantSpringWeb participant. It creates a Spring application context using a normal Spring XML configuration file, then allows the Spring-managed beans to be referenced as properties from RIFE code.
If you provide a parameter to this participant, it will be used to look up the context XML declaration through the ContextLoader
class as a value for the CONFIG_LOCATION_PARAM
context-param.
If you provide no parameter, Spring will try to look up an existing web application context in the servlet context.
Injecting Spring beans into RIFE elements
RIFE's IoC support can inject Spring beans into RIFE elements. This allows you to use Spring's sophisticated IoC dependency management to wire up an arbitrarily complex set of service objects, then make use of those objects from your RIFE code without mixing RIFE and Spring API calls.
First, we need to tell RIFE to instantiate a SpringWeb participant. The participant is declared like any other in participants.xml:
<participant param="classpath:rep/spring/applicationContext.xml">ParticipantSpringWeb</participant>
Here we are placing the Spring application context configuration file in a subdirectory of our RIFE repository directory. Configuring Spring is amply documented on the Spring web site and in numerous books; we won't dwell on the details here, but for example's sake we'll use this simple configuration:
<beans default-autowire="byType">
<bean id="productDao" class="com.foo.dao.ProductDao" />
<bean id="loggingService" class="com.foo.misc.LoggingServiceImpl" />
</beans>
There are two beans here, which Spring will wire together based on their types and available setters using its usual autowiring rules.
Now we can make the productDao bean available to RIFE elements. This is done by declaring the bean as a property in the element configuration. The easiest place to do it is in the top-level site definition, but you can also declare properties on a per-element basis if you wish (see the IoC support page for details.)
<site>
<property name="datasource">
<datasource>postgresql</datasource>
</property>
<property name="productDao">
<participant name="ParticipantSpringWeb">productDao</participant>
</property>
<element id="CATALOG" implementation="com.foo.element.Catalog" url="catalog" />
</site>
There are two properties declared here; the first is a normal RIFE Datasource and the second is the productDao bean we defined in the Spring configuration. The property name on the RIFE side does not have to be the same as the bean name on the Spring side, but it's probably a good idea for clarity's sake. (See below for a way to eliminate the need to list Spring beans in the RIFE configuration.)
The Catalog element can fetch the Spring bean explicitly:
ProductDao dao = (ProductDao) getProperty("productDao");
Or it can provide a setter method which RIFE will automatically call based on the bean's name:
private ProductDao mDao;
public void setProductDao(ProductDao dao) {
mDao = dao;
}
Injecting RIFE objects into Spring beans
But what if our Spring beans need to use some of RIFE's infrastructure? For example, what if the DAO above was implemented using RIFE's database APIs? The solution here is a little uglier, but still relatively straightforward.
The basic idea is to write a factory class that is first instantiated as a RIFE participant and pulls the needed resources from RIFE, storing them in static member variables. Then the same class is instantiated again by Spring as a bean factory, and it returns the resources it previously obtained from RIFE. In essence, static member variables are used as a conduit between RIFE and Spring.
Here is an example of such a class; it provides RIFE Datasource objects to Spring beans.
package com.foo.util;
import com.uwyn.rife.database.Datasource;
import com.uwyn.rife.rep.BlockingParticipant;
import com.uwyn.rife.rep.exceptions.ParticipantNotFoundException;
/**
* Supplies RIFE Datasource objects to Spring-managed beans. This should be
* added as a RIFE participant before the Spring participant and after the
* datasource participant. The parameter should be the name of the
* datasource participant.
*/
public class DatasourceFactory extends BlockingParticipant {
private static Datasource mDatasource;
private static boolean mInitializationFinished = false;
private static Object mMonitor = new Object();
/**
* RIFE-side initialization. This is called first and looks up the
* Datasource.
*/
@Override
public void initialize() {
setInitializationMessage("Looking up Datasource");
setCleanupMessage("Cleaning up Datasource");
String param = getParameter();
if (param == null)
param = "datasource";
mDataSource = (Datasource) getRepository()
.getParticipant("ParticipantDatasources")
.getObject(param);
synchronized (_monitor) {
mInitializationFinished = true;
mMonitor.notifyAll();
}
if (mDataSource == null) {
throw new ParticipantNotFoundException("datasource " + param);
}
}
/**
* Spring initialization method.
*/
public void init() {
}
/**
* Waits until we are finished initializing.
*/
public void waitUntilInitialized() {
synchronized (mMonitor) {
while (! mInitializationFinished) {
try {
mMonitor.wait();
}
catch (InterruptedException e) { }
}
}
}
/**
* Returns a Datasource instance. Since Spring might fire up before
* RIFE initializes us, we need to wait for RIFE's initialization to
* finish first.
*/
public Datasource getInstance() {
waitUntilInitialized();
return mDatasource;
}
}
This class uses a RIFE helper class to implement most of the required methods in the Participant interface. The only Participant method we care about is the initialize() method, where we fetch the object we want from RIFE. (If you want to supply more than one kind of RIFE object to your Spring beans, you can do it all from the same factory class; just write a different getter method for each type.)
Next, we install the factory class as a RIFE participant. Its parameter is the name of the particular datasource we want to use.
<rep>
<participant param="rep/config.xml">ParticipantConfig</participant>
<participant param="rep/datasources.xml">ParticipantDatasources</participant>
<participant param="postgresql">com.foo.util.DatasourceFactory</participant>
<participant param="classpath:rep/spring/applicationContext.xml">ParticipantSpringWeb</participant>
<participant param="sites/main.xml">ParticipantSite</participant>
</rep>
On the Spring side, we declare the class as a factory and declare the Datasource as a factory-generated bean:
<beans default-autowire="byType">
<bean id="productDao" class="com.foo.dao.ProductDaoRife" />
<bean id="datasourceFactory" class="com.foo.util.DatasourceFactory" />
<bean id="datasource" class="com.uwyn.rife.database.Datasource"
factory-bean="datasourceFactory" factory-method="getInstance" />
</beans>
And now the product DAO object will automatically have its Datasource setter called before Spring hands it out to callers.
Naturally, we can combine this with the technique in the preceding section to inject this DAO object into our RIFE elements – allowing the full power of Spring's dependency management to mix freely with RIFE's infrastructure classes and elements.
Cutting down on redundant configuration
One problem with the approach so far is that we end up having to repeat each of our Spring bean declarations: once in the Spring configuration and once in the RIFE configuration so that RIFE can inject them into our elements. Wouldn't it be nice if the Spring beans were automatically available for RIFE injection?
The answer is to subclass the ParticipantSpringWeb class; after it sets up the Spring application context, it gets a list of all the beans from Spring and puts them in the RIFE global properties list.
package com.foo.util;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import com.uwyn.rife.ioc.HierarchicalProperties;
import com.uwyn.rife.ioc.PropertyValueObject;
import com.uwyn.rife.rep.participants.ParticipantSpringWeb;
/**
* Spring application context that automatically makes all Spring beans
* available as global parameters to RIFE elements. Use this instead of
* the plain ParticipantSpringWeb when you want to be able to inject most
* or all of your Spring beans into RIFE elements.
*/
public class AutowiringParticipantSpringWeb extends ParticipantSpringWeb {
/**
* Initializes the participant. Allows the superclass to set up the
* Spring application context, then queries the context for all the
* available beans and adds them to the RIFE repository's global
* properties.
*/
@Override
protected void initialize() {
super.initialize();
ListableBeanFactory factory = (ListableBeanFactory) _getObject();
if (factory != null) {
String[] beanNames = BeanFactoryUtils.beanNamesIncludingAncestors(factory);
if (beanNames != null) {
HierarchicalProperties props = getRepository().getProperties();
for (String beanName : beanNames) {
Object bean = factory.getBean(beanName);
props.put(beanName, new PropertyValueObject(bean));
}
}
}
}
}
Use this participant in place of ParticipantSpringWeb:
<participant param="classpath:rep/spring/applicationContext.xml">
com.foo.util.AutowiringParticipantSpringWeb
</participant>
And now all our Spring beans will be autowired by name into our RIFE elements. The downside to this approach is that it exposes all of the Spring beans to RIFE, whether or not they will ever be injected into RIFE elements. Avoid reusing the names of the Spring beans on the RIFE side and this should not present any problems in practice.