xml-custom.xml 31.9 KB
Newer Older
T
Thomas Risberg 已提交
1
<?xml version="1.0" encoding="UTF-8"?>
2 3
<!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
T
Thomas Risberg 已提交
4

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
<appendix id="extensible-xml">
    <title>Extensible XML authoring</title>
    <section id="extensible-xml-introduction">
        <title>Introduction</title>
        <para>Since version 2.0, Spring has featured a mechanism for schema-based extensions
        to the basic Spring XML format for defining and configuring beans. This section is
        devoted to detailing how you would go about writing your own custom XML bean definition
        parsers and integrating such parsers into the Spring IoC container.</para>
    	<para>To facilitate the authoring of configuration files using a schema-aware XML editor,
    	Spring's extensible XML configuration mechanism is based on XML Schema. If you are
    	not familiar with Spring's current XML configuration extensions that come with the
    	standard Spring distribution, please first read the appendix entitled
    	<xref linkend="xsd-config"/>.</para>
    	<para>Creating new XML configuration extensions can be done by following these (relatively)
    	simple steps:</para>
    	<para>
    	    <orderedlist numeration="arabic">
                <listitem>
                    <para><link linkend="extensible-xml-schema">Authoring</link> an XML schema to describe your custom element(s).</para>
                </listitem>
                <listitem>
                    <para><link linkend="extensible-xml-namespacehandler">Coding</link> a custom <interfacename>NamespaceHandler</interfacename>
                    implementation (this is an easy step, don't worry).</para>
                </listitem>
                <listitem>
                    <para><link linkend="extensible-xml-parser">Coding</link> one or more <interfacename>BeanDefinitionParser</interfacename>
                    implementations (this is where the real work is done).</para>
                </listitem>
                <listitem>
                    <para><link linkend="extensible-xml-registration">Registering</link> the above artifacts with Spring (this too is an easy step).</para>
                </listitem>
    	    </orderedlist>
    	</para>
    	<para>What follows is a description of each of these steps. For the example, we will create
    	an XML extension (a custom XML element) that allows us to configure objects of the type
    	<classname>SimpleDateFormat</classname> (from the <literal>java.text</literal> package)
    	in an easy manner. When we are done, we will be able to define bean definitions of type
    	<classname>SimpleDateFormat</classname> like this:</para>
43
		<programlisting language="xml"><![CDATA[<myns:dateformat id="dateFormat" 
44 45 46 47 48 49 50 51 52 53 54 55 56
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>
]]></programlisting>
        <para><emphasis>(Don't worry about the fact that this example is very simple; much more
        detailed examples follow afterwards. The intent in this first simple example is to walk
        you through the basic steps involved.)</emphasis></para>
    </section>
    <section id="extensible-xml-schema">
    	<title>Authoring the schema</title>
    	<para>Creating an XML configuration extension for use with Spring's IoC container
    	starts with authoring an XML Schema to describe the extension. What follows
    	is the schema we'll use to configure <classname>SimpleDateFormat</classname>
    	objects.</para>
57
    	<programlisting language="xml"><lineannotation>&lt;!-- myns.xsd (inside package org/springframework/samples/xml) --&gt;</lineannotation><![CDATA[
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

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

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

   <xsd:element name="dateformat">
      <xsd:complexType>
         <xsd:complexContent>]]>
            <emphasis role="bold"><![CDATA[<xsd:extension base="beans:identifiedType">]]></emphasis><![CDATA[
               <xsd:attribute name="lenient" type="xsd:boolean"/>
               <xsd:attribute name="pattern" type="xsd:string" use="required"/>
            </xsd:extension>
         </xsd:complexContent>
      </xsd:complexType>
   </xsd:element>

</xsd:schema>]]></programlisting>
        <para>(The emphasized line contains an extension base for all tags that
    	will be identifiable (meaning they have an <literal>id</literal> attribute
    	that will be used as the bean identifier in the container). We are able to use this
    	attribute because we imported the Spring-provided <literal>'beans'</literal>
    	namespace.)</para>
		<para>The above schema will be used to configure <classname>SimpleDateFormat</classname>
		objects, directly in an XML application context file using the
		<literal>&lt;myns:dateformat/&gt;</literal> element.</para>
89
		<programlisting language="xml"><![CDATA[<myns:dateformat id="dateFormat" 
90 91 92 93 94 95 96 97
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>
]]></programlisting>
		<para>Note that after we've created the infrastructure classes, the above snippet of XML
		will essentially be exactly the same as the following XML snippet. In other words,
		we're just creating a bean in the container, identified by the name
		<literal>'dateFormat'</literal> of type <classname>SimpleDateFormat</classname>, with a
		couple of properties set.</para>
98
		<programlisting language="xml"><![CDATA[<bean id="dateFormat" class="java.text.SimpleDateFormat">
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    <constructor-arg value="yyyy-HH-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>]]></programlisting>
		<note>
			<para>The schema-based approach to creating configuration format allows for
			tight integration with an IDE that has a schema-aware XML editor. Using a properly
			authored schema, you can use autocompletion to have a user choose between several
			configuration options defined in the enumeration.</para>
		</note>
	</section>
	<section id="extensible-xml-namespacehandler">
		<title>Coding a <interfacename>NamespaceHandler</interfacename></title>
		<para>In addition to the schema, we need a <interfacename>NamespaceHandler</interfacename>
		that will parse all elements of this specific namespace Spring encounters
		while parsing configuration files. The <interfacename>NamespaceHandler</interfacename>
		should in our case take care of the parsing of the <literal>myns:dateformat</literal>
		element.</para>
		<para>The <interfacename>NamespaceHandler</interfacename> interface is pretty simple in that 
		it features just three methods:</para>
		<itemizedlist spacing="compact">
			<listitem>
				<para><methodname>init()</methodname> - allows for initialization of
				the <interfacename>NamespaceHandler</interfacename> and will be called by Spring
				before the handler is used</para>
			</listitem>
			<listitem>
				<para><methodname>BeanDefinition parse(Element, ParserContext)</methodname> - 
				called when Spring encounters a top-level element (not nested inside a bean definition
				or a different namespace). This method can register bean definitions itself and/or
				return a bean definition.</para>
			</listitem>
			<listitem>
				<para><methodname>BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)</methodname> -
				called when Spring encounters an attribute or nested element of a different namespace.
				The decoration of one or more bean definitions is used for example with the
				<link linkend="beans-factory-scopes">out-of-the-box	scopes Spring 2.0 supports</link>.
				We'll start by highlighting a simple example, without using decoration, after which
				we will	show decoration in a somewhat more advanced example.</para>
			</listitem>
		</itemizedlist>
		<para>Although it is perfectly possible to code your own
		<interfacename>NamespaceHandler</interfacename> for the entire namespace
		(and hence provide code that parses each and every element in the namespace),
		it is often the case that each top-level XML element in a Spring XML
		configuration file results in a single bean definition (as in our
		case, where a single <literal>&lt;myns:dateformat/&gt;</literal> element
		results in a single <classname>SimpleDateFormat</classname> bean definition).
		Spring features a number of convenience classes that support this scenario.
		In this example, we'll make use the <classname>NamespaceHandlerSupport</classname> class:</para>
148
		<programlisting language="java"><![CDATA[package org.springframework.samples.xml;
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177

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

public class MyNamespaceHandler extends NamespaceHandlerSupport {
    
    public void init() {]]><emphasis role="bold"><![CDATA[
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());        
    ]]></emphasis>}
}</programlisting>
        <para>The observant reader will notice that there isn't actually a whole lot of
        parsing logic in this class. Indeed... the <classname>NamespaceHandlerSupport</classname>
        class has a built in notion of delegation. It supports the registration of any number
        of <interfacename>BeanDefinitionParser</interfacename> instances, to which it will delegate
        to when it needs to parse an element in its namespace. This clean separation of concerns
        allows a <interfacename>NamespaceHandler</interfacename> to handle the orchestration
        of the parsing of <emphasis>all</emphasis> of the custom elements in its namespace,
        while delegating to <literal>BeanDefinitionParsers</literal> to do the grunt work of the
        XML parsing; this means that each <interfacename>BeanDefinitionParser</interfacename> will
        contain just the logic for parsing a single custom element, as we can see in the next step</para>
	</section>
	<section id="extensible-xml-parser">
		<title>Coding a <interfacename>BeanDefinitionParser</interfacename></title>
		<para>A <interfacename>BeanDefinitionParser</interfacename> will be used if the
		<interfacename>NamespaceHandler</interfacename> encounters an XML element of the type
		that has been mapped to the specific bean definition parser (which is <literal>'dateformat'</literal>
		in this case). In other words, the <interfacename>BeanDefinitionParser</interfacename> is
		responsible for parsing <emphasis>one</emphasis> distinct top-level XML element defined in the
		schema. In the parser, we'll have access to the XML element (and thus its subelements too)
		so that we can parse our custom XML content, as can be seen in the following example:</para>
178
		<programlisting language="java"><![CDATA[package org.springframework.samples.xml;
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import java.text.SimpleDateFormat;

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { ]]><co id="extensible-xml-parser-simpledateformat-co-1"/><![CDATA[

   protected Class getBeanClass(Element element) {
      return SimpleDateFormat.class; ]]><co id="extensible-xml-parser-simpledateformat-co-2"/><![CDATA[
   }

   protected void doParse(Element element, BeanDefinitionBuilder bean) {
      ]]><lineannotation>// this will never be null since the schema explicitly requires that a value be supplied</lineannotation><![CDATA[
      String pattern = element.getAttribute("pattern");
      bean.addConstructorArg(pattern);

      ]]><lineannotation>// this however is an optional property</lineannotation><![CDATA[
      String lenient = element.getAttribute("lenient");
      if (StringUtils.hasText(lenient)) {
         bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
      }
   }
}]]></programlisting>
		<calloutlist>
			<callout arearefs="extensible-xml-parser-simpledateformat-co-1">
			    <para>We use the Spring-provided <classname>AbstractSingleBeanDefinitionParser</classname>
			    to handle a lot of the basic grunt work of creating a <emphasis>single</emphasis>
			    <interfacename>BeanDefinition</interfacename>.</para>
			</callout>
			<callout arearefs="extensible-xml-parser-simpledateformat-co-2">
			    <para>We supply the <classname>AbstractSingleBeanDefinitionParser</classname> superclass
			    with the type that our single <interfacename>BeanDefinition</interfacename> will represent.</para>
			</callout>
		</calloutlist>
		<para>In this simple case, this is all that we need to do. The creation of our single
		<interfacename>BeanDefinition</interfacename> is handled by the <classname>AbstractSingleBeanDefinitionParser</classname>
		superclass, as is the extraction and setting of the bean definition's unique identifier.</para>
	</section>
	<section id="extensible-xml-registration">
		<title>Registering the handler and the schema</title>
		<para>The coding is finished! All that remains to be done is to somehow make the Spring XML
		parsing infrastructure aware of our custom element; we do this by registering our custom
		<interfacename>namespaceHandler</interfacename> and custom XSD file in two special purpose
		properties files. These properties files are both placed in a
		<filename class="directory">'META-INF'</filename> directory in your application, and can, for
		example, be distributed alongside your binary classes in a JAR file. The Spring XML parsing
		infrastructurewill automatically pick up your new extension by consuming these special
		properties files, the formats of which are detailed below.</para>
		<section id="extensible-xml-registration-spring-handlers">
			<title><filename>'META-INF/spring.handlers'</filename></title>
			<para>The properties file called <filename>'spring.handlers'</filename> contains a mapping
			of XML Schema URIs to namespace handler classes. So for our example, we need to write the
			following:</para>
			<programlisting><![CDATA[http\://www.mycompany.com/schema/myns=org.springframework.samples.xml.MyNamespaceHandler]]></programlisting>
			<para><emphasis>(The <literal>':'</literal> character is a valid delimiter in the Java properties format,
			and so the <literal>':'</literal> character in the URI needs to be escaped with a backslash.)</emphasis></para>
			<para>The first part (the key) of the key-value pair is the URI associated with your custom namespace
			extension, and needs to <emphasis>match exactly</emphasis> the value of the
			<literal>'targetNamespace'</literal> attribute as specified in your custom XSD schema.</para>
		</section>
		<section id="extensible-xml-registration-spring-schemas">
			<title><filename>'META-INF/spring.schemas'</filename></title>
			<para>The properties file called <filename>'spring.schemas'</filename> contains a mapping
			of XML Schema locations (referred to along with the schema declaration in XML files
			that use the schema as part of the <literal>'xsi:schemaLocation'</literal> attribute)
			to <emphasis>classpath</emphasis> resources. This file is needed to prevent Spring from
			absolutely having to use a default <interfacename>EntityResolver</interfacename> that requires
			Internet access to retrieve the schema file. If you specify the mapping in this properties file,
			Spring will search for the schema on the classpath (in this case <literal>'myns.xsd'</literal>
			in the <literal>'org.springframework.samples.xml'</literal> package):</para>
			<programlisting><![CDATA[http\://www.mycompany.com/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd]]></programlisting>
			<para>The upshot of this is that you are encouraged to deploy your XSD file(s) right alongside
			the <interfacename>NamespaceHandler</interfacename> and <interfacename>BeanDefinitionParser</interfacename>
			classes on the classpath.</para>
		</section>
	</section>
	<section id="extensible-xml-using">
		<title>Using a custom extension in your Spring XML configuration</title>
		<para>Using a custom extension that you yourself have implemented is no different from
		using one of the 'custom' extensions that Spring provides straight out of the box. Find below
		an example of using the custom <literal>&lt;dateformat/&gt;</literal> element developed in the
		previous steps in a Spring XML configuration file.</para>
264
		<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
265 266 267 268
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:myns="http://www.mycompany.com/schema/myns"
      xsi:schemaLocation="
269
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">

   ]]><lineannotation>&lt;!-- as a top-level bean --&gt;</lineannotation><![CDATA[
   <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>

   <bean id="jobDetailTemplate" abstract="true">
      <property name="dateFormat">
         ]]><lineannotation>&lt;!-- as an inner bean --&gt;</lineannotation><![CDATA[
         <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
      </property>
   </bean>

</beans>]]></programlisting>
	</section>
	<section id="extensible-xml-meat">
		<title>Meatier examples</title>
		<para>Find below some much meatier examples of custom XML extensions.</para>
	    <section id="extensible-xml-custom-nested">
		    <title>Nesting custom tags within custom tags</title>
		    <para>This example illustrates how you might go about writing the various artifacts
		    required to satisfy a target of the following configuration:</para>
291
		    <programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
292 293 294 295
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:foo="http://www.foo.com/schema/component"
      xsi:schemaLocation="
296
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
297 298 299
http://www.foo.com/schema/component http://www.foo.com/schema/component/component.xsd">

   ]]><lineannotation><![CDATA[<foo:component id="bionic-family" name="Bionic-1">
C
Costin Leau 已提交
300 301 302 303
      <foo:component name="Mother-1">
        <foo:component name="Karate-1"/>
        <foo:component name="Sport-1"/>
      </foo:component>
304 305 306 307 308 309 310 311 312 313 314
      <foo:component name="Rock-1"/>
   </foo:component>]]></lineannotation><![CDATA[

</beans>]]></programlisting>
            <para>The above configuration actually nests custom extensions within each other. The class
            that is actually configured by the above <literal>&lt;foo:component/&gt;</literal>
            element is the <classname>Component</classname> class (shown directly below). Notice
            how the <classname>Component</classname> class does <emphasis>not</emphasis> expose
            a setter method for the <literal>'components'</literal> property; this makes it hard
            (or rather impossible) to configure a bean definition for the <classname>Component</classname>
            class using setter injection.</para>
315
            <programlisting language="java"><![CDATA[package com.foo;
316 317 318 319 320 321 322

import java.util.ArrayList;
import java.util.List;

public class Component {

   private String name;
C
Costin Leau 已提交
323
   private List<Component> components = new ArrayList<Component> ();
324 325 326 327 328 329

   ]]><lineannotation>// mmm, there is no setter method for the <literal>'components'</literal></lineannotation><![CDATA[
   public void addComponent(Component component) {
      this.components.add(component);
   }

C
Costin Leau 已提交
330
   public List<Component> getComponents() {
331 332 333 334 335 336 337 338 339 340 341 342 343
      return components;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}]]></programlisting>
            <para>The typical solution to this issue is to create a custom <interfacename>FactoryBean</interfacename>
            that exposes a setter property for the <literal>'components'</literal> property.</para>
344
            <programlisting language="java"><![CDATA[package com.foo;
345 346 347 348 349

import org.springframework.beans.factory.FactoryBean;

import java.util.List;

C
Costin Leau 已提交
350
public class ComponentFactoryBean implements FactoryBean<Component> {
351 352

   private Component parent;
C
Costin Leau 已提交
353
   private List<Component> children;
354 355 356 357 358

   public void setParent(Component parent) {
      this.parent = parent;
   }

C
Costin Leau 已提交
359
   public void setChildren(List<Component> children) {
360 361 362
      this.children = children;
   }

C
Costin Leau 已提交
363
   public Component getObject() throws Exception {
364
      if (this.children != null && this.children.size() > 0) {
C
Costin Leau 已提交
365 366
         for (Component child : children) {
            this.parent.addComponent(child);
367 368 369 370 371
         }
      }
      return this.parent;
   }

C
Costin Leau 已提交
372
   public Class<Component> getObjectType() {
373 374 375 376 377 378 379 380 381 382 383 384
      return Component.class;
   }

   public boolean isSingleton() {
      return true;
   }
}]]></programlisting>
            <para>This is all very well, and does work nicely, but exposes a lot of Spring plumbing to the
            end user. What we are going to do is write a custom extension that hides away all of this
            Spring plumbing. If we stick to <link linkend="extensible-xml-introduction">the steps described
            previously</link>, we'll start off by creating the XSD schema to define the structure of
            our custom tag.</para>
385
            <programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405

<xsd:schema xmlns="http://www.foo.com/schema/component"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
         targetNamespace="http://www.foo.com/schema/component"
         elementFormDefault="qualified"
         attributeFormDefault="unqualified">

   <xsd:element name="component">
      <xsd:complexType>
         <xsd:choice minOccurs="0" maxOccurs="unbounded">
            <xsd:element ref="component"/>
         </xsd:choice>
         <xsd:attribute name="id" type="xsd:ID"/>
         <xsd:attribute name="name" use="required" type="xsd:string"/>
      </xsd:complexType>
   </xsd:element>

</xsd:schema>
]]></programlisting>
            <para>We'll then create a custom <interfacename>NamespaceHandler</interfacename>.</para>
406
            <programlisting language="java"><![CDATA[package com.foo;
407 408 409 410 411 412 413 414 415 416 417 418

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

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

   public void init() {
      registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
   }
}]]></programlisting>
            <para>Next up is the custom <interfacename>BeanDefinitionParser</interfacename>. Remember
            that what we are creating is a <interfacename>BeanDefinition</interfacename> describing
            a <classname>ComponentFactoryBean</classname>.</para>
419
            <programlisting language="java"><![CDATA[package com.foo;
420

C
Costin Leau 已提交
421
import org.springframework.beans.factory.config.BeanDefinition;
422 423 424 425 426 427 428 429 430 431 432 433 434
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.List;

public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {

   protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
C
Costin Leau 已提交
435 436 437 438
      return parseComponentElement(element);
   }
   
   private static AbstractBeanDefinition parseComponentElement(Element element) {
439
      BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
C
Costin Leau 已提交
440 441 442
      factory.addPropertyValue("parent", parseComponent(element));
      
      List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
443 444 445
      if (childElements != null && childElements.size() > 0) {
         parseChildComponents(childElements, factory);
      }
C
Costin Leau 已提交
446

447 448 449
      return factory.getBeanDefinition();
   }

C
Costin Leau 已提交
450
   private static BeanDefinition parseComponent(Element element) {
451 452
      BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
      component.addPropertyValue("name", element.getAttribute("name"));
C
Costin Leau 已提交
453
      return component.getBeanDefinition();
454
   }
C
Costin Leau 已提交
455 456 457 458 459 460
   
   private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
      ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
     
      for (Element element : childElements) {
         children.add(parseComponentElement(element));
461
      }
C
Costin Leau 已提交
462
     
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
      factory.addPropertyValue("children", children);
   }
}]]></programlisting>
            <para>Lastly, the various artifacts need to be registered with the Spring XML infrastructure.</para>
            <programlisting><lineannotation># in <filename>'META-INF/spring.handlers'</filename></lineannotation><![CDATA[
http\://www.foo.com/schema/component=com.foo.ComponentNamespaceHandler]]></programlisting>
            <programlisting><lineannotation># in <filename>'META-INF/spring.schemas'</filename></lineannotation><![CDATA[
http\://www.foo.com/schema/component/component.xsd=com/foo/component.xsd]]></programlisting>
	    </section>
	    <section id="extensible-xml-custom-just-attributes">
		    <title>Custom attributes on 'normal' elements</title>
		    <para>Writing your own custom parser and the associated artifacts isn't hard, but sometimes it
		    is not the right thing to do. Consider the scenario where you need to add metadata to already
		    existing bean definitions. In this case you certainly don't want to have to go off and write
		    your own entire custom extension; rather you just want to add an additional attribute
		    to the existing bean definition element.</para>
		    <para>By way of another example, let's say that the service class that you are defining a bean
		    definition for a service object that will (unknown to it) be accessing a clustered
		    <ulink url="http://jcp.org/en/jsr/detail?id=107">JCache</ulink>, and you want to ensure that
		    the named JCache instance is eagerly started within the surrounding cluster:</para>
483
		    <programlisting language="xml"><![CDATA[<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
484 485 486 487 488 489 490 491 492
      ]]><lineannotation><emphasis role="bold">jcache:cache-name="checking.account"&gt;</emphasis></lineannotation><![CDATA[
   ]]><lineannotation>&lt;!-- other dependencies here... --&gt;</lineannotation><![CDATA[
</bean>]]></programlisting>
            <para>What we are going to do here is create another <interfacename>BeanDefinition</interfacename>
            when the <literal>'jcache:cache-name'</literal> attribute is parsed; this
            <interfacename>BeanDefinition</interfacename> will then initialize the named JCache
            for us. We will also modify the existing <interfacename>BeanDefinition</interfacename> for the
            <literal>'checkingAccountService'</literal> so that it will have a dependency on this
            new JCache-initializing <interfacename>BeanDefinition</interfacename>.</para>
493
            <programlisting language="java"><![CDATA[package com.foo;
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

public class JCacheInitializer {

   private String name;

   public JCacheInitializer(String name) {
      this.name = name;
   }

   public void initialize() {
      ]]><lineannotation>// lots of JCache API calls to initialize the named cache...</lineannotation><![CDATA[
   }
}]]></programlisting>
            <para>Now onto the custom extension. Firstly, the authoring of the XSD schema describing the
            custom attribute (quite easy in this case).</para>
509
            <programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
510 511 512 513 514 515 516 517 518 519 520

<xsd:schema xmlns="http://www.foo.com/schema/jcache"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.foo.com/schema/jcache"
            elementFormDefault="qualified">

   <xsd:attribute name="cache-name" type="xsd:string"/>

</xsd:schema>
]]></programlisting>
            <para>Next, the associated <interfacename>NamespaceHandler</interfacename>.</para>
521
            <programlisting language="java"><![CDATA[package com.foo;
522 523 524 525 526 527 528 529 530 531 532 533 534 535

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

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

   public void init() {
      super.registerBeanDefinitionDecoratorForAttribute("cache-name",
            new JCacheInitializingBeanDefinitionDecorator());
   }
}
]]></programlisting>
            <para>Next, the parser. Note that in this case, because we are going to be parsing an XML
            attribute, we write a <interfacename>BeanDefinitionDecorator</interfacename> rather than a
            <interfacename>BeanDefinitionParser</interfacename>.</para>
536
            <programlisting language="java"><![CDATA[package com.foo;
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606

import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
   
   private static final String[] EMPTY_STRING_ARRAY = new String[0];

   public BeanDefinitionHolder decorate(
         Node source, BeanDefinitionHolder holder, ParserContext ctx) {
      String initializerBeanName = registerJCacheInitializer(source, ctx);
      createDependencyOnJCacheInitializer(holder, initializerBeanName);
      return holder;
   }

   private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder, String initializerBeanName) {
      AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
      String[] dependsOn = definition.getDependsOn();
      if (dependsOn == null) {
         dependsOn = new String[]{initializerBeanName};
      } else {
         List dependencies = new ArrayList(Arrays.asList(dependsOn));
         dependencies.add(initializerBeanName);
         dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
      }
      definition.setDependsOn(dependsOn);
   }

   private String registerJCacheInitializer(Node source, ParserContext ctx) {
      String cacheName = ((Attr) source).getValue();
      String beanName = cacheName + "-initializer";
      if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
         BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
         initializer.addConstructorArg(cacheName);
         ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
      }
      return beanName;
   }
}
]]></programlisting>
            <para>Lastly, the various artifacts need to be registered with the Spring XML infrastructure.</para>
            <programlisting><lineannotation># in <filename>'META-INF/spring.handlers'</filename></lineannotation><![CDATA[
http\://www.foo.com/schema/jcache=com.foo.JCacheNamespaceHandler]]></programlisting>
            <programlisting><lineannotation># in <filename>'META-INF/spring.schemas'</filename></lineannotation><![CDATA[
http\://www.foo.com/schema/jcache/jcache.xsd=com/foo/jcache.xsd]]></programlisting>
		</section>
	</section>
	<section id="extensible-xml-resources">
		<title>Further Resources</title>
		<para>Find below links to further resources concerning XML Schema and the extensible XML support
		described in this chapter.</para>
		<itemizedlist>
			<listitem>
				<para>The <ulink url="http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/">XML Schema Part 1: Structures Second Edition</ulink></para>
			</listitem>
			<listitem>
				<para>The <ulink url="http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/">XML Schema Part 2: Datatypes Second Edition</ulink></para>
			</listitem>
		</itemizedlist>
	</section>
</appendix>