Custom namespace parser in Spring

on waitingforcode.com

Custom namespace parser in Spring

In Spring we can configure dependencies in several ways: with XML files or directly in Java using annotations. This first way is usual considered as clearer and simpler to maintain. And if you're working with it, you certainly met some of Spring XML namespaces, as those one for Spring Security or Spring MVC projects. In this article we'll focus on this aspect of Spring.

At the begin of this article we'll start by talking about namespaces in general. We'll see how they work and how to create new one. At the second part we'll dive in into Spring part which works with XML configuration and namespaces. The last part of the article will treat about creating our own namespace with bean parser.

What is XML namespace ?

Naming freedom of XML is an advantage and disadvantage. From the one side, we can name our elements and attributes almost without any limitations. But from the other side, this freedom can lead into name conflicts. Imagine following situation:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<messages>
  <template>
    <header>My header</header>
    <content>Hello world !</content>
  </template>
  <template>
    <div>
      <h1>Welcome to our store</h1>
      <span>You can start to make shopping</span>
    </div>
  </template>
</messages>

As you can see, our sample XML file contains two different meanings of <template /> element. This double presence of this element can lead into multiple conflict problems because the first "template" is destined to e-mail message and the other for HTML page. In additionally, different children elements can be accepted on both cases, for example: header and content for e-mail one and div, h1, span for HTML one. To avoid that, a simple technique exists - using of namespaces.

Namespace is an Uniform Resource Identifier (URI) which helps to identify different elements in XML document. It helps to tell, for example, that one node can accept only some type of children or attributes. It could be ideally used in our "template naming" conflict to indicate which node corresponds to e-mail and which one for HTML specification:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<messages xmlns:email="http://www.waitingforcode.com/schema/email"
             xmlns:html="http://www.waitingforcode.com/schema/html">
>
  <email:template>
    <email:header>My header</email:header>
    <email:content>Hello world !</email:content>
  </email:template>
  <html:template>
    <html:div>
      <html:h1>Welcome to our store</html:h1>
      <html:span>You can start to make shopping</html:span>
    </html:div>
  </html:template>
</messages>

Unlike the first sample, we can observe the presence of "xmlns:${prefix}". This attribute defines the namespace to apply on given element. Namespaces are identified by ${prefix}. This prefix must precede nodes to which given namespace applies (as html:div, email:header). What we should expect to find in namespace schemas ? These schemas contain the list of elements which are allowed in given namespace. For example, to tell that only header and content are accepted in e-mail template, we could write something like:

 
<xs:element name="template">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="header" type="xs:string" />
      <xs:element name="content" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

Namespace parsing in Spring

To see how Spring deals with XML namespaces, we'll start by breaking down the parsing mechanism. We'll do that by adding invalid attribute to <bean /> element. Faked bean definition looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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-3.1.xsd">
    <bean name="sampleBean" class="com.waitingforcode.beans.SampleBean" fictiveAttribute="OK" />
</beans>

When you start to launch your webapp, you should see following exception:

Infos: Loading XML bean definitions from ServletContext resource [/WEB-INF/classes/META-INF/app-config.xml]
août 06, 2014 19:39:35 PM org.springframework.web.servlet.DispatcherServlet initServletBean
Grave: Context initialization failed
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 5 in XML document from ServletContext resource [/WEB-INF/classes/META-INF/app-config.xml] is invalid; 
nested exception is org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 97; cvc-complex-type.3.2.2 : L'attribut 'fictiveAttribute' n'est pas autorisé dans l'élément 'bean
'.
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:398)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:335)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:303)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:216)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:187)
        at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
        at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
        at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
        at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:540)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:454)
        at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:658)
        at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:624)
        at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:672)
        at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:543)
        at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:484)
        at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
        at javax.servlet.GenericServlet.init(GenericServlet.java:158)
        at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1279)
        at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1192)
        at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1087)
        at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5210)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5493)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:632)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1073)
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1857)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
        at java.util.concurrent.FutureTask.run(FutureTask.java:262)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)
Caused by: org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 97; cvc-complex-type.3.2.2 : L'attribut 'fictiveAttribute' n'est pas autorisé dans l'élément 'bean'.
        at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198)
        at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437)
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368)
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:325)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:458)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3237)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processAttributes(XMLSchemaValidator.java:2714)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2056)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:766)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:355)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2770)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:347)
        at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:76)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:428)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
        ... 33 more

If you're looking carefully on it, you can see that all XML manipulation is made by Apacher Xerces project. From Spring's side, we can simply find a interpreter for XML beans (XmlBeanDefinitionReader). We've already talking in the article about application context in Spring. Because it's important member of bean processing, we'll remind a little bit about it. Bean definition readers are in charge of analyzing XML configuration document and transmitting defined properties to an instance of org.springframework.beans.factory.support.BeanDefinitionBuilder. Thanks to this last object we can discover the programmatic composition (constructor arguments, properties...) of given bean.

Another key point in XML configuration is namespace handler. It's based on org.springframework.beans.factory.xml.NamespaceHandler interface. We can find there 3 methods:
- void init() : called by DefaultBeanDefinitionDocumentReader after the construction of this NamespaceHandler implementation, but before the begin of parse.
- BeanDefinition parse(Element element, ParserContext parserContext) : parses given element being an instance of org.w3c.dom.Element.
- BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) : another parsing method which deal with BeanDefinitionHolder. It's called for elements belonging to different namespace that the namespace of NamespaceHandler. It can return original BeanDefinitionHolder (passed in method's signature) or new one, decorated with retrieved elements from Node source parameter.

An example of NamespaceHandler implementation can be found for all "namespacable" elements in XML configuration file (mvc:, tx:, aop:, jdbc:, util: and so on). They're all extending org.springframework.beans.factory.xml.NamespaceHandlerSupport abstract class. One of them, ContextNamespaceHandler, looks like:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

  @Override
  public void init() {
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
    registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
    registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
  }

}

In this init() method we can find all XML attributes allowed in elements using context namespace, as for example this fragment which scans package containing all controllers:

  <context:component-scan base-package="com.waitingforcode.controller"/>

Custom namespace in Spring

Now when we know which elements are mandatory to implement custom namespace in Spring configuration file, we can start to write one. First, we need to define our validation schema:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.waitingforcode.com/schema/wfc"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:beans="http://www.springframework.org/schema/beans"
  targetNamespace="http://www.waitingforcode.com/schema/wfc"
  elementFormDefault="qualified"
  attributeFormDefault="unqualified">

  <xsd:import namespace="http://www.springframework.org/schema/beans"/>

  <xsd:element name="beanWfc">
    <xsd:complexType>
      <xsd:complexContent>
        <xsd:extension base="beans:identifiedType">
          <xsd:attribute name="objectClass" type="xsd:string" use="required"/>
          <xsd:attribute name="scope" type="xsd:string"/>
        </xsd:extension>
      </xsd:complexContent>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

This schema is more advanced than the schema from the first part because it contains the references to another schemas (Spring's bean schema in our case). You can also see that we import one namespace to use bean:identifiedType. A identifiedType is a bean that can be identified, ie. bean containing an id attribute. We extends this type by adding two supplementary supported attributes: objectClass and scope. The first one defines the class of bean while the second one the scope. It may seem redundant with Spring beans but the presence of "scope" attribute is justified. As you'll see further, this attribute'll be used to override scope defined in Spring beans by invoking decorate() method, presented in the previous part of the article.

After discovering schema, we can define it into two files: META-INF/spring.handlers and META-INF/spring.schemas. Thanks to these two files, we'll able to use our schema locally, without the necessity to download it from internet:

# META-INF/spring.handlers
# It contains the mapping between schemas and namespace handlers
# The values are resolved thanks to org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver
http\://www.waitingforcode.com/schema/wfc=com.waitingforcode.namespace.WfcBeanNamespaceHandler

# META-INF/spring.schemas
# We must put here the mapping between schemas used in XML files (in xsi:schemaLocation) and the 
# physical location of these schemas in classpath
http\://www.waitingforcode.com/schema/wfc/wfc.xsd=META-INF/schemas/wfc.xsd

Sample configuration used to illustrate schema customization in Spring is:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:wfc="http://www.waitingforcode.com/schema/wfc"
       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-4.0.xsd
       http://www.waitingforcode.com/schema/wfc http://www.waitingforcode.com/schema/wfc/wfc.xsd">
    <wfc:beanWfc id="frenchHello" objectClass="com.waitingforcode.beans.FrenchHelloWorld" />
    <bean class="com.waitingforcode.beans.EnglishHelloWorld" name="englishHelloWorld" wfc:scope="singleton" scope="prototype" />
</beans>

Note that in the case of Spring-resolved bean, attribute scope appears twice: once as a part of Spring schema and once as a part of our wfc schema. Let's see now our namespace handler:

public class WfcBeanNamespaceHandler extends NamespaceHandlerSupport {

  // attributes availables for bean definition
  public enum ATTRS {
      objectClass;
  }

  @Override
  public void init() {
    /**
      * First, we need to register appropriate decorators and parsers. After, NamespaceHandlerSupport
      * will use findDecoratorForNode(Node node, ParserContext parserContext) and
      * findParserForElement(Element element, ParserContext parserContext) methods
      * to pick up the right parser or decorator for given element.
      */
    registerBeanDefinitionDecoratorForAttribute("scope", new WfcBeanDefinitionDecorator());
    registerBeanDefinitionParser("beanWfc", new WfcBeanDefinitionParser());
  }

  @Override
  public BeanDefinition parse(Element element, ParserContext parserContext) {
    return super.parse(element, parserContext);
    /**
      * Two ways exist to implement custom namespace handler. The first, and the easiest one,
      * consists on use NamespaceHandlerSupport which resolves itself decorate and parse process.
      *
      * The another one, more complicated, consists on implement
      * {@link org.springframework.beans.factory.xml.NamespaceHandler} interface and, for example,
      * define all parsing operations in parse and decorate methods. You can find this
      * implementation commented below.
      *
      * Note also if that you're using the simplest choice, your bean must contain "id" attribute.
      * Otherwise, following exception can be thrown at runtime:
      * <pre>
      * Caused by: org.springframework.beans.factory.parsing.BeanDefinitionParsingException:
      * Configuration problem: Configuration problem: Id is required for element 'beanWfc' when used as a top-level tag
      *   Offending resource: class path resource [META-INF/app-config.xml]
      *   Offending resource: class path resource [META-INF/app-config.xml]
      *     at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70)
      *     at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
      *     at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:72)
      *     at org.springframework.beans.factory.xml.AbstractBeanDefinitionParser.parse(AbstractBeanDefinitionParser.java:83)
      *     at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:74)
      * // ...
      * </pre>
      */

    /*
        NamespaceHandler implementation
      */
    /**
      * As you can see, the goal of this method is to construct BeanDefinition
      * directly from XML definition of passed Element.
      * <pre>
      * BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
      * builder.getRawBeanDefinition().setBeanClassName(element.getAttribute(ATTRS.objectClass.name()));
      * String beanName = element.getAttribute(ATTRS.beanName.name());
      * </pre>
      *
      * This call is mandatory. Otherwise, beans injection won't work and NoSuchBeaNDefinitionException
      * will be thrown:
      * <pre>
      * parserContext.getRegistry().registerBeanDefinition(beanName,  builder.getBeanDefinition());
      * </pre>
      *
      * And, at the end, return constructed BeanDefinition object:
      * <pre>
      * return builder.getBeanDefinition();
      * </pre>
      */
  }

  @Override
  public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder beanDefinitionHolder, ParserContext parserContext) {
    return super.decorate(node, beanDefinitionHolder, parserContext);
    /**
      * NamespaceHandler implementation
      *
      * Decorate method is invoked every time when this namespace element is present in element
      * belonging to another namespace. For example, imagine that you created NamespaceHandler
      * for schema which elements are prefixed by "mySchema". If you mix it with classical
      * Spring beans, you should receive following thing:
      * <pre>
      * <bean name="testBean" class="com.waitingforcode.beans.TestBean" mySchema:idNumber="#39" />
      * </pre>
      *
      * When Spring will process this bean definition, it'll see that one attribute belongs to
      * different namespace. Spring will calls decorate() method of appropriate NamespaceHandler
      * and "decorate" parsed bean with supplementary elements.
      *
      * This example illustrates how to write decorator which overrides bean scope.
      * <pre>
      * if (node instanceof Attr) {
      *   Attr attr = (Attr) node;
      *   if (attr.getName().equals("wfc:scope")) {
      *     System.out.println("Scope before change: "+beanDefinitionHolder.getBeanDefinition().getScope());
      *     beanDefinitionHolder.getBeanDefinition().setScope(attr.getValue());
      *     System.out.println("Scope after change: "+beanDefinitionHolder.getBeanDefinition().getScope());
      *   }
      * }
      * return beanDefinitionHolder;
      * </pre>
      */
  }
}

The code is a little bit verbose but it's because of comments. Without them, it could be very compact because we need only to register an element (tag) parser and a decorator for wfc:scope attribute which appears in Spring-resolved bean. There are these two classes:

public class WfcBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

  @Override
  protected void doParse(Element element, BeanDefinitionBuilder beanDefBuilder) {
    /**
      * As you can see, the goal of this method is to construct BeanDefinition
      * directly from XML definition of passed Element.
      */
    beanDefBuilder.getRawBeanDefinition().setBeanClassName(element.getAttribute(WfcBeanNamespaceHandler.ATTRS.objectClass
            .name()));
  }

}

public class WfcBeanDefinitionDecorator implements BeanDefinitionDecorator {

  @Override
  public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder beanDefinitionHolder, ParserContext parserContext) {
    if (node instanceof Attr) {
      Attr attr = (Attr) node;
      if (attr.getName().equals("wfc:scope")) {
        System.out.println("Scope before change: "+beanDefinitionHolder.getBeanDefinition().getScope());
        beanDefinitionHolder.getBeanDefinition().setScope(attr.getValue());
        System.out.println("Scope after change: "+beanDefinitionHolder.getBeanDefinition().getScope());
      }
    }
    return beanDefinitionHolder;
  }
}

To check if they work correctly, we'll define a simple test case:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/META-INF/app-config.xml"})
public class NamespaceTest {

  @Autowired
  @Qualifier(value = "frenchHello")
  private HelloWorldInterface helloWorldWfc;

  @Autowired
  @Qualifier(value = "englishHelloWorld")
  private HelloWorldInterface helloWorldSpring;

  @Autowired
  private ApplicationContext context;

  @Test
  public void checkWfcBean() {
    assertTrue("helloWorldWfc bean shouldn't be null", helloWorldWfc != null);
    assertTrue("helloWorldSpring bean shouldn't be null", helloWorldSpring != null);
    assertTrue("helloWorldSpring should be an instance of EnglishHelloWorld", helloWorldSpring instanceof EnglishHelloWorld);
    assertTrue("helloWorldWfc should be an instance of FrenchHelloWorld", helloWorldWfc instanceof FrenchHelloWorld);
    assertTrue("englishHelloWorld should be singleton scoped", context.isSingleton("englishHelloWorld"));
  }

}

First, we test here if both beans were correctly injected. If it's the case we can think that our parser doesn't block Spring's one. After that we verify if both beans have expected implementations. At the end we check if our decorator worked well and changed the scope of Spring-resolved bean. Normally, all tests should pass correctly.

In this article we could see how to implement custom namespace handler. At the first part we're focused on XML part of this subject by exploring XML schemas. After that we presented which elements are used by Spring when it tries to translate XML bean into beans. The last part was more pragmatic and illustrated how to implement custom Spring's namespace handler.

Share on: