Thursday, March 18, 2010

Coherence with Spring on OSGi - Oh My!

OSGi has gotten some real traction as an Enterprise services platform. For one of the projects I was involved in a viability study for Coherence with Spring on OSGi recently. Even though not all can be divulged here but we overcame some interesting challenges that are worth to blog about.

Why OSGi? Why Spring? and Why Coherence (Is that a question)?

  • OSGi - It has championed the modularity.
  • Spring - Dependency Injection probably is still its biggest strengths.
  • Coherence - Solves World Peace with simplicity, elegance and Powerful APIs.
Challenges:
  1. OSGi - It needs patience. Lots of it specially if you are just starting with it. You do not need to change the application code to be OSGi compliant. It requires a specific MANIFEST.MF that describes the bundle's attributes and packages it exports and imports with other bundle dependencies. Make sure you have BND handy. I would not recommend you writing the Manifest file your own. Let BND do it for you. Equinox and Knopflerfish are two very popular platforms. I used Equinox.
  2. Spring - Do not work with vanilla Spring APIs. You would need Spring Dynamic Modules that are Spring for OSGi. Also download Springsource Tool Suit. You would need some jars from it's STS to be installed. Also make sure you deploy the 3.0.0 version of the libraries. It will solve a lot of pain you would rather end up going through.
  3. Coherence - Coherence does not have an OSGi bundle off the shelf so you would need to create one. Creating an OSGi bundle for Coherence is fairly easy by using Eclipse IDE. File->New->Project->Plug-in from Existing jar Archives->Select coherence.jar and follow the rest of the instructions. Export the artifact. That's it. Watch out for serialization issues if multiple modules are involved.
Spring DMs:
Install the following modules in the same order (use appropriate start levels):
  1. com.springsource.org.aopalliance
  2. com.springsource.org.apache.commons.logging
  3. org.springframework.aop (3.0.0.RELEASE)
  4. org.springframework.asm ( 3.0.0.RELEASE)
  5. org.springframework.beans (3.0.0.RELEASE)
  6. org.springframework.context (3.0.0.RELEASE)
  7. org.springframework.core (3.0.0.RELEASE)
  8. org.springframework.expression (3.0.0.RELEASE)
  9. org.springframework.osgi.core (1.2.1)
  10. org.springframework.osgi.extender (1.2.1)
  11. org.springframework.osgi.io (1.2.1)
  12. org.springframework.transaction (3.0.0.RELEASE)
  13. com.tangosol.coherence (Or whatever name is given for the Coherence bundle created)
Don't shy away by restarting the Equinox a few times - you may need it.

Lets build a service: Say we want to build a service that puts a String key and a String value in a given data source.

public interface IPut {
public void put (String ds, String key, String value);
}

For an ability to replace Coherence as a Datasource with another lets use a Template that can be injected by Spring later with Application remaining agnostic to the data source provider.

public interface ITemplate {
public void put(String ds, String key, String val);
}

Even though IPut and ITemplate are similar it doesn't have to be. IPut represents the exposed service and ITemplate is for persistence.

Implementations:

public class Putter implements IPut {
private ITemplate template;

public Putter(ITemplate template) {
this.template = template;
}

public void put (String ds, String key, String value) {
if (template != null) {
template.put(ds, key, value);
}
}
}

And,

public class CoherenceTemplate implements ITemplate {

public CoherenceTemplate() {
}

public void put (String cacheName, String key, String value) {
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
ClassLoader newLoader = com.tangosol.net.CacheFactory.class.getClassLoader();
Thread.currentThread().setContextClassLoader(newLoader);
NamedCache nCache = CacheFactory.getCache (cacheName);
nCache.put (key, value);
Thread.currentThread().setContextClassLoader(oldLoader);"

}

}


Make sure you do have appropriate coherence-cache-config.xml deployed.

Configurations:
Spring beans.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:osgi="http://www.springframework.org/schema/osgi"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd>

<bean name="template" class="CoherenceTemplate" scope="prototype">
</bean>

<bean name="daoService" class="Putter">
<constructor-arg ref="template"/>
</bean>

</beans>


That's not it. We need another configuration to expose this service for OSGi:

<?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:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<osgi:service id="daoOSGiService" ref="daoService"
interface="IPut"></osgi:service>
</beans>


It's also important to place these two configurations under META-INF/spring directory.

What about the MANIFEST.MF?
Use bundlor to generate the MANIFEST.MF for you. You will have to copy the manifest back to the project and then re-bundle the final artifact jar.

Once you have it all together thats all you need to deploy this application on OSGi.

What about the Client now?
Client would be another OSGi bundle and the process of developing it is similar. As it is still all Spring driven make sure the services are appropriately injected.

First the OSGi configuration:

<?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:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<osgi:reference id="daoService" interface="IPut"/>
</beans>

Then for other beans:

<?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.xsd">
<bean name="persister" class="MyClient"
init-method="start" destroy-method="stop">
<property name="putter" ref="daoService"/>
</bean>
</beans>


And the client is like any typical Spring client:

public class MyClient {
IPut putter;

public MyClient () {
}

public IPut getPutter() {
return putter;
}

public void setPutter(IPut putter) {
this.putter = putter;
}

public void start() throws Exception {
putter.put ("MyDataSource", "key", "Value");
}

public void stop() throws Exception {
System.out.println("Goodbye");
}
}


Don't forget to use the BND again to generate the manifest and re-bundling it with the client artifact jar.

Now deploy the client jar and start it.

Enjoy!

No comments: