Sunday, November 30, 2008

Environment specific property with Spring

In an application there are lots of properties which vary from one environment to another e.g. database used in development will be different from the one used in production, leading to different connection properties (url, user names and password). Applications using Spring Framework usually fallback on PropertyPlaceholderConfigurer for inserting such values from an external properties file.

I would like to have everything at one place instead of externalizing properties into separate file for each environment. Fortunately Spring Framework provides extension points to create user defined XML elements and classes to instantiate beans using those elements. To have environment specific properties in single configuration file along with other bean definitions, I decided to create a custom element named <property>. Next I will try to explain how it works.

Here is the schema for <property> element, which is used to define different property for each environment-
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://vinodsingh.com/schema/spring"
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                   targetNamespace="http://vinodsingh.com/schema/spring">

    <xsd:element name="property">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" use="required" />
            <xsd:attribute name="class" type="xsd:string" use="optional" default="java.lang.String" />
            <xsd:attribute name="value" type="xsd:string" use="required" />
            <xsd:attribute name="region" use="optional" default="all">
                <xsd:simpleType>
                    <xsd:restriction base="xsd:string">
                        <xsd:enumeration value="dev" />
                        <xsd:enumeration value="test" />
                        <xsd:enumeration value="integ" />
                        <xsd:enumeration value="stage" />
                        <xsd:enumeration value="uat" />
                        <xsd:enumeration value="prod" />
                        <xsd:enumeration value="all" />
                    </xsd:restriction>
                </xsd:simpleType>
            </xsd:attribute>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
Now create a bean definition parser to parse the <property> element-
public class PropertyDefinitionParser extends AbstractSingleBeanDefinitionParser {

    /** Region where bean will be available. */
    private String beanRegion = null;

    private static String sysRegion = System.getProperty("region");

    static {
        if (sysRegion == null || sysRegion.length() <= 0)
            sysRegion = "all";
    }

    @Override
    protected Class<?> getBeanClass(Element element) {
        Class<?> clazz = null;
        String className = element.getAttribute("class");
        if (className != null && className.length() > 0) {
            try {
                clazz = Class.forName(className);
            } catch (Exception e) {
                log.error("Failed to load class: " + className, e);
                throw new RuntimeException(e);
            }
        }

        return clazz;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        beanRegion = element.getAttribute("region");

        bean.addConstructorArgValue(element.getAttribute("value"));
    }

    @Override
    protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
        if (beanRegion.equals(sysRegion) || "all".equals(beanRegion))
            super.registerBeanDefinition(definition, registry);
    }
}
Based on the above-mentioned schema the resulting bean definitions will looks like-
<property id="jdbc.url" value="jdbc:hsqldb:hsql://dev:9002" region="dev" />
<property id="jdbc.url" value="jdbc:hsqldb:hsql://production:9002" region="prod" />

<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="driverClassName"/>
    <property name="url" ref="jdbc.url"/>
    <property name="username" value="username"/>
    <property name="password" value="password"/>
</bean>
Here jdbc.url property is defined twice with different region value. In bean definition for dataSource the jdbc.url is used as bean reference. The PropertyDefinitionParser will register only one definition of jdbc.url,which belongs to the current runtime region. The region will be passed as a system property to JVM.

Saturday, November 29, 2008

Packaging dynamic resources with Maven

Maven is an excellent project management tool which makes difficult things easy. With doing so many things it also bundles resource files if we tell it the path to resources as shown below-
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    . . .
    <properties>
        <resource.dir>${code.base.dir}/${project.artifactId}/src/main/resources</resource.dir>
    </properties>
    . . .
    <build>
        . . .
    <resources>
            <resource>
                <directory>${resource.dir}</directory>
            </resource>
        </resources>
        . . .
    </build>
</project>
Above configuration works well for the resources, which are already present along with source code. There are cases when these resources are generated during build process like WSDL for a web service project. Though usually it is not required but in certain use cases it may be required to bundle such dynamically generated resources. As I mentioned in my earlier post about issues being faced in consuming web services in an array of applications based on SOA Architecture. In this particular scenario bundling WSDL will be really helpful, I will discuss more about it in coming posts.

To include generated WSDLs we can decalre their directory as resource directory and invoke the wsgen and wsimport tasks to generate the artificats-
    <properties>
        <wsdl.dir>${target.dir}/jaxws/wsgen/wsdl</wsdl.dir>
        <resource.dir>${wsdl.dir}/..</resource.dir>
        <keep.src>false</keep.src>
        <verbose>true</verbose>
    </properties>
    . . .
    <build>
        <plugins>
             <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxws-maven-plugin</artifactId>
                <version>${jaxws-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <id>My Service</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wsgen</goal>
                        </goals>
                        <configuration>
                            <sei>pkg.MyServiceImpl</sei>
                            <destDir></destDir>
                            <genWsdl>true</genWsdl>
                            <keep>${keep.src}</keep>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxws-maven-plugin</artifactId>
                <version>${jaxws-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <id>My Service</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wsimport</goal>
                        </goals>
                        <configuration>
                            <wsdlDirectory>${wsdl.dir}</wsdlDirectory>
                            <wsdlFiles>
                                <wsdlFile>MyService.wsdl</wsdlFile>
                            </wsdlFiles>
                            <packageName>pkg.wsclient</packageName>
                            <target>2.0</target>
                            <keep>${keep.src}</keep>
                            <verbose>${verbose}</verbose>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
In the above example <phase>generate-sources</phase> declaration is very important. If phase is defined as compile then WSDL files won't be included in the jar as by that time Maven has already decided what to be included and from where. Similar tricks can be used to include other kind of dynamically generated content as well.

Sunday, November 23, 2008

Consuming web services with Spring

Consuming web services with Spring framework is amazingly easy. It avoids the need of creating client side stubs during compile time and does the same at run time. A typical web service client can be configured as shown below-

<bean id="myWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="serviceInterface" value="pkg.MyService"/>
    <property name="wsdlDocumentUrl" value="http://localhost:8080/testws/myService?wsdl"/>
    <property name="namespaceUri" value="http://com.pkg/"/>
    <property name="serviceName" value="MyService"/>
    <property name="portName" value="MyServicePort"/>
</bean>
Where serviceInterface is the business interface that clients will use for invoking service methods. wsdlDocumentUrl is the URL for the published WSDL file. Spring will download WSDL from the given URL and generate the client side stubs when it creates the bean usually during application start up. namespaceUri corresponds to the targetNamespace in the .wsdl file. serviceName corresponds to the service name in the .wsdl file. portName corresponds to the port name in the .wsdl file.

Now accessing web service pretty easy, just get the bean from Spring context and use that to invoke methods-
MyService myService = springContext.getBean("myWebService");
myService.someMethod(. . .);
One downside of this approach is that the web service must be up running when consumer application is being deployed. To avoid this problem one can use lazy-init="true" to lazily initialize the beans when they are first requested but all this quickly becomes unmanageable when we have an array of SOA based applications. In such a scenario one have to strictly follow the order of application deployment else deployment might fail.