Spring 2.0: Hiding Services Behind Custom XML Schema (Part II)

In the first part of this two-part blog entry, I attempted to show the advantages of service-specific custom xml schema from the point of view of the person using the service – the client code developer. As in any service-client relationship, the goal is to make things easy for the client, if necessary at the expense of the service. What I intend to show in this entry is how [tag]Spring 2.0[/tag] allows service developers to offer custom XML schema, and how this activity can be streamlined enough to make it an attractive propostition to the service developer.

Keeping in mind that conceptually what we’re actually doing here is translating from a custom xml tag into one or more standard <bean> tags, here’s what a developer needs to do to offer a custom schema as part of an API:

  1. Write an XSD to describe the service, in its own unique target namespace.
  2. Implement the BeanDefinitionParser interface, which knows how to create one or more bean definitions, given a custom tag.
  3. Write a NamespaceHandler to associate a custom tag with its BeanDefintionParser.
  4. Map the XSD’s unique namespace to a NamespaceHandler using a META-INF/spring.handler file.
  5. Map the XSD’s unique namespace to the the xsd resource using a META-INF/spring.schemas file.

Of these tasks, the most important and challenging are writing a good XSD and implementing the BeanDefinitionParser. The other items are trivial both in terms of time and skill required.

A Good Service XSD
The XSD should be written with the following in mind: This is what the client developer is going to depend on most when using the service. Create a custom tag for each independant service bean, giving them attributes that correspond to overwriteable properties. Even overwritable properties that belong to ‘private’ beans – i.e. beans other than the service beans themselves – should be associated with attributes of the service bean’s custom tag. Attributes that expect references rather than values should use a suitable naming convenstion (e.g. ‘Ref’ as a attribute name suffix). Required properties should have required attributes. If possible, there should be no child element defined – I think it would be hard to justify that kind of complexity here.

Here’s an example of a service XML schema that describes four services (customer, replocation,email and password). Note that it in turn derives from this schema – more on that below.

Avoiding Programmatic Bean Declaration
The BeanDefinitionParser implementations in the [tag]Springframework[/tag] itself programatically create and register BeanDefinitions that lie behind their schema tags. It makes sense to do so in that context, as there are only about 5 of these parsers so far, and when they very occasionally need to be updated, it will be done by the Spring developers, who are specialists. But here in DSI we create new service projects almost weekly, and our development expertise does not always include the inner working of Spring’s own source code (nor should it have to). Asking our service developers to code and maintain the programatic creation of beans that they had previously described using Spring configuration files would be a backwards step (and our developers would tell us so in very direct terms!)

What is needed in this kind of environment is a parser that simply reads a service Spring configuration file, creating and registering the beans it finds there (My thanks to our own Anthony Geoghegan for putting me on the right track in this respect), aliasing the service beans and setting or overwriting bean properties, based on the client custom tag values. We’ve implemented such a parser such that when it is triggered by a tag like <cs:customer name=”fred”/> in the client configuration it finds and parses its service config file, and aliases the service bean whose id is customer to fred. The code to do this is straightforward, reusable by any service, requiring only that the service developer respect the naming convention. With this the kind of support, a service developer can begin to consider using a customer schema. Here’s the NamespaceHandler that associates the customerservice tag to this generalized parser:

import ie.dsi.springschema.ServiceBeanDefinitionParser;

import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class LraServiceNamespaceHandler extends NamespaceHandlerSupport {  

  public LraServiceNamespaceHandler() {
      
      BeanDefinitionParser parser = new ServiceBeanDefinitionParser(
            "/ie/decaresystems/lraservice/config/serviceContext.xml");

      registerBeanDefinitionParser("customer", parser);
      registerBeanDefinitionParser("replocation", parser);
      registerBeanDefinitionParser("email", parser);
      registerBeanDefinitionParser("password", parser);
  }
}

Note the reusable ServiceBeanDefinitionParser, which can only be instanciated with a String pointing to the service Spring configuration resource. It is this NamespaceHandler class that associates the four customer service tags with the shared parser instance.

Setting Service Bean Attributes
But what if the service bean requires a property to be set by the client? Again the generalized parser can work here as long as naming conventions are followed. In our example the customerservice bean needs a reference to a datasource that is to be provided by the client. The XSD expresses this through a required attribute of name dataSourceRef. If the service bean itself has a dataSource property, then the client configuration <customerservice name=”fred” dataSourceRef=”myDatasource”/> will trigger our generalized parser to set the dataSource property of the service bean instance with id customerservice to a reference to a bean called myDatasource (something which the service expects to be defined by the client).

Setting Plumbing Bean Attributes
Even for quite simple scenarios, the functionality described about still is not enough. Take the example email custom tag. According to the XSD this element has an attribute called smtpHostname. In reality, the email service bean behind this tag has no such property. Instead, in its plumbing, it has an instance of JavaMailSenderImpl injected, which in turn has a String property called host. The service developer wants to expose that string property without exposing the implementation details of the email service bean. This can be done by simply redirecting the exposed smtpHostname attribute to the JavaMailSenderImpl instance’s host property. By registering this redirect in our service parser, we can override the default behaviour for that attribute. The resulting NamespaceHandler code would look almost the same as above but with the following extra code:

      . . .

      BeanDefinitionParser parser = new ServiceBeanDefinitionParser(
            "/ie/decaresystems/lraservice/config/serviceContext.xml");
      parser.registerAttributeRedirect("customer", "smtpHostName", "mailSender", "host");

      . . .

What the above code does is configure the parser to deviate from its standard property-setting behaviour where the tag being parsed is ‘customer’ and the attribute is ‘smtpHostName’, setting the ‘host’ property of the ‘mailSender’ bean instead. With this simple property redirection device, most services are catered for. So this generic ServiceBeanParser means that the service developer will not need to write their own parser – just the (reasonably trivial) NamespaceHandler. Now the only non trivial task is the writing of the XSD.

Moving Glue Code Back to the Config File
To make things easier again, it makes sense at this stage to move the property redirection code shown in the last section back into the service’s Spring configuration. This has the virtue of making the NamespaceHandler truely trivial, containing only the name of the service config file and the names of the public service beans. The parser can read the redirection bean (identified automatically by type) and register these redirections as part of its initialization. Creating our own generic ServiceNamespace handler, we can reduce the handcrafted code to the following:


public class AvonLraNamespaceHandler extends ServiceNamespaceHandler {
  
    public AvonLraNamespaceHandler() {
      super("/corporate/avon/service/config/serviceContext.xml");

      registerServiceBeanDefinition("customer");
      registerServiceBeanDefinition("email");
      registerServiceBeanDefinition("replocation");
      registerServiceBeanDefinition("password");
  }
}


At this point we seem quite close to being able to do away with this instance entirely, by using some file and bean naming convention – but I’m not sure if this can be done in such a way as to allow more than one service to be used. Perhaps there’ll be a third part to this blog!

Back to Where We Started
The property redirection bean configuration just mentioned would look something like this:

    <bean id="servicePropertyRedirects" class="ie.dsi.springschema.PropertyRedirecter">
        <property name="redirects">
          <list>
             <bean class="ie.dsi.springschema.PropertyRedirect">
              <property name="sourceElementName" value="email"/>
              <property name="sourceAttributeName" value="smtpHostname"/>
              <property name="targetBeanName" value="mailSender"/>
              <property name="targetBeanProperty" value="host"/>
             </bean>
          </list>
        </property>
    </bean>

But of course at this point we could write one more once-off custom schema to render it more easily:

    <service:redirects>
      <service:redirect sourceElement="email" sourceAttribute="smtpHostname"
                        targetBean="mailSender" targetProperty="host"/>
    </service:redirects>

But that really would take a third entry! I think that’ll do for now.

Just to recap: We’ve looked at what it takes to make a custom schema, and taking into account what would be an acceptable overhead for exposing a service using such a schema, we’ve whittled down this work to something close to it’s minimum: The design of an XSD file to describe a service, and the creation of 3 trival java and text files (trivial enough to be generated by an ant task or eclipse plugin, for example). Anyone interested in the java files behind this should contact me at blawlor AT decaresystems DOT ie.

– brendan lawlor –

  1. #1 by Brendan Lawlor on July 20, 2006 - 5:16 pm

    The code behind this idea has been added as a requested feature on Spring’s JIRA:

    http://opensource.atlassian.com/projects/spring/browse/MOD-164

    – brendan

  2. #2 by HD Trailer on May 18, 2011 - 8:34 am

    Thanks for this! I’ll check this site everyday and looking for some posts like this.

  3. #3 by Download bot Kiếm Rồng on August 5, 2011 - 4:31 pm

    Thanks for this! I’ll check this site everyday and looking for some posts like this.

  1. fix your credit

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: