forked from spring-projects/spring-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxml-custom.xml
More file actions
606 lines (538 loc) · 31.9 KB
/
xml-custom.xml
File metadata and controls
606 lines (538 loc) · 31.9 KB
1
2
3
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
89
90
91
92
93
94
95
96
97
98
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
148
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
178
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
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
<?xml version="1.0" encoding="UTF-8"?>
<appendix xmlns="http://docbook.org/ns/docbook" version="5.0"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xi="http://www.w3.org/2001/XInclude"
xml: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>
<programlisting language="xml"><![CDATA[<myns:dateformat id="dateFormat"
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>
<programlisting language="xml"><lineannotation><!-- myns.xsd (inside package org/springframework/samples/xml) --></lineannotation><![CDATA[
<?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><myns:dateformat/></literal> element.</para>
<programlisting language="xml"><![CDATA[<myns:dateformat id="dateFormat"
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>
<programlisting language="xml"><![CDATA[<bean id="dateFormat" class="java.text.SimpleDateFormat">
<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><myns:dateformat/></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>
<programlisting language="java"><![CDATA[package org.springframework.samples.xml;
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>
<programlisting language="java"><![CDATA[package org.springframework.samples.xml;
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
infrastructure will 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><dateformat/></literal> element developed in the
previous steps in a Spring XML configuration file.</para>
<programlisting language="xml"><![CDATA[<?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:myns="http://www.mycompany.com/schema/myns"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
]]><lineannotation><!-- as a top-level bean --></lineannotation><![CDATA[
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
<bean id="jobDetailTemplate" abstract="true">
<property name="dateFormat">
]]><lineannotation><!-- as an inner bean --></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>
<programlisting language="xml"><![CDATA[<?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:foo="http://www.foo.com/schema/component"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.foo.com/schema/component http://www.foo.com/schema/component/component.xsd">
]]><lineannotation><![CDATA[<foo:component id="bionic-family" name="Bionic-1">
<foo:component name="Mother-1">
<foo:component name="Karate-1"/>
<foo:component name="Sport-1"/>
</foo:component>
<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><foo:component/></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>
<programlisting language="java"><![CDATA[package com.foo;
import java.util.ArrayList;
import java.util.List;
public class Component {
private String name;
private List<Component> components = new ArrayList<Component> ();
]]><lineannotation>// mmm, there is no setter method for the <literal>'components'</literal></lineannotation><![CDATA[
public void addComponent(Component component) {
this.components.add(component);
}
public List<Component> getComponents() {
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>
<programlisting language="java"><![CDATA[package com.foo;
import org.springframework.beans.factory.FactoryBean;
import java.util.List;
public class ComponentFactoryBean implements FactoryBean<Component> {
private Component parent;
private List<Component> children;
public void setParent(Component parent) {
this.parent = parent;
}
public void setChildren(List<Component> children) {
this.children = children;
}
public Component getObject() throws Exception {
if (this.children != null && this.children.size() > 0) {
for (Component child : children) {
this.parent.addComponent(child);
}
}
return this.parent;
}
public Class<Component> getObjectType() {
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>
<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<programlisting language="java"><![CDATA[package com.foo;
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>
<programlisting language="java"><![CDATA[package com.foo;
import org.springframework.beans.factory.config.BeanDefinition;
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) {
return parseComponentElement(element);
}
private static AbstractBeanDefinition parseComponentElement(Element element) {
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
factory.addPropertyValue("parent", parseComponent(element));
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
if (childElements != null && childElements.size() > 0) {
parseChildComponents(childElements, factory);
}
return factory.getBeanDefinition();
}
private static BeanDefinition parseComponent(Element element) {
BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
component.addPropertyValue("name", element.getAttribute("name"));
return component.getBeanDefinition();
}
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));
}
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>
<programlisting language="xml"><![CDATA[<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
]]><lineannotation><emphasis role="bold">jcache:cache-name="checking.account"></emphasis></lineannotation><![CDATA[
]]><lineannotation><!-- other dependencies here... --></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>
<programlisting language="java"><![CDATA[package com.foo;
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>
<programlisting language="xml"><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<programlisting language="java"><![CDATA[package com.foo;
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>
<programlisting language="java"><![CDATA[package com.foo;
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>