Welcome to the JXV tutorial. This tutorial introduces the main features of JXV in a step-by-step format walking through a particular JXV application, the PhoneBook application. Before we go into the details of this particular example, this introduction section will explain what exactly is JXV, where it stands in the XML-binders arena, and where it should be used. The topics in the tutorial were ordered so that they can be read by a new user in the order they appear without having to jump forward. However, if you already know JXV you can use it as a reference as well.
As a first step, you should make sure that the JXV distribution is working properly on your system. If you do not have the JXV distribution yet, you can download the latest release from http://jxv.sourceforge.net. JXV requires JDK 1.3 or higher. Once you get the distribution, run the order sample:
Go into the "samples" folder in the JXV distribution.
Run the JXVTester as follows (replace the "c:\temp" or "/temp" with some folder where you want the XML to be written).
On MS Windows: "java -classpath .;../lib/jxv.jar -Dorg.jxv.config.config-files=./test/order/config.xml org.jxv.test.JXVTester -rw c:\temp test.order.OrderBatchFactory 1 10 10"
On Bash shells: "java -classpath .:../lib/jxv.jar -Dorg.jxv.config.ConfigDir=./order-config org.jxv.test.JXVTester -rw /temp test.order.OrderBatchFactory 1 10 10"
Convert this command line to the format your own shell system.
(the command-line arguments are explained in the JXVTester section).
You should see something like:
Iteration 1 started...
write operation: 2200 millis
read operation: 281 millis
rewrite operation: 47 millis
written and re-written files are identical
Original and read object matched
The folder you specified in the command line should now contain two files, "OrderBatchFactory1.xml" and "OrderBatchFactory1-rewrite.xml". Open one of them and take a look at the generated XML.
Once you had gone through these steps and everything worked, you are ready to start using JXV.
JXV is a framework that translates Java objects into XML format. It supports the the two major XML APIs (SAX and DOM) and also supports reading XML back into objects. XML namespaces are fully supported and integrated into the configuration mechanism. JXV is highly configurable, and almost none of it's behavior is hard-coded in the source. An XML config file is used to load the factories that create views, and configure how those factories create the views. You can extend JXV by writing new view factories, although JXV's existing factories are flexible and are sufficient for most purposes.
JXV is a type of XML Data Binder. There are two major types of data binders:
It is usually clear which type of binder you need. If you already have objects and you want to treat them as XML for some reason (several possible reasons will be given below) you need a mapper of the first kind. If you have an XML format, and you want to represent it using classes so you can process it in Java, you need a mapper of the second kind.
JXV fits into the first category. As it's name implies, it gives Java objects "XML views". In this category there are also two sub-categories. Some mappers generate a "normal" XML format to represent your objects. For instance, a PhoneBook object with a collection of contacts might look something like this:
Other mappers generate some specific object-markup format of their own. The same example as above might look something like:
This second type is not really a mapper, but rather a type of a serialization framework that happens to use XML as it's serialization format. JXV again fits into the first category. The second category is useful mainly when you want to read/write your objects and you prefer a text-based format for some reason. It is very hard to process the mapper-specific format in languages other than Java, or to apply XML applications such as XSLT and XPath on it. Which brings us to...
JXV can be used anywhere where you can benefit from an XML representation of Java objects. In fact, JXV was designed with this goal as a key focus (in contrast to just XML read/write), and it supports some unique features that promote this goal and are not available in most XML mappers. The reflective DOM tree is an example of this. It is described the following section. So, the question is, when can you benefit from having an XML representation of your objects? Here is a list of some things you might want to do where an XML representation of objects can be helpful:
I'm sure there are lots of other scenarios not listed here. XML is very popular and there is an ever growing number of standards and applications that facilitate the processing of XML documents. It is the goal of JXV to act as a bridge between the Java object world and the XML document world so that these standards and applications can also be applied to object graphs.
In this section you will learn the basic concepts and terminology of JXV. Perhaps the most basic concept is the concept of a View Factory. View factories are factory objects that create, given a specific configuration context (see below), an XML view or a View Reader. There are two types of XML Views:
DOM Views: a DOM view consists of a list of DOM nodes (e.g., elements, text) and a set of DOM attributes. This is an important point which sometimes contradicts the intuition of new users, who expect every object to be represented by exactly one node. The reason for this conflict in intuition is a basic mismatch that occurs when you translate objects into XML (see the note below). But first let's define the other type of XML view.
SAX Views: a SAX view is the SAX-equivalent of a DOM view. It has methods to emit it's list of SAX events to a SAX event listener, and a method to return it's attribute set. SAX Views are usually more efficient if you just want to write the XML once.
View Readers are objects that take a stream of SAX events to treat as an XML view of an object, and read it back into an object. JXV's view readers generally implement a single-lookahead algorithm. This means that in contexts where the reader doesn't know which view will come next (i.e. it has multiple alternative views), it will look only one event ahead when deciding which view is in fact the correct one. An example may clarify this. Consider an Expression class and two subclasses, AndExpression and OrExpression. When the JXV reader tries to read an Expression, it would first try to read it as if it were an AndExpression. If the first event matches (for instance the first element in the AndExpression view is the same as the next element in the SAX stream), then the reader would now become sure that it is in fact reading an AndExpression. If the rest of the view isn't correctly structured, it would throw an exception. However, if the first element doesn't match the first element of the AndExpression view, then the reader would try to interpret the view as an OrExpression. Single-lookahead algorithms are generally more efficient than algorithms that remember chunks of the stream while reading it, because they don't waste any memory. At every point in the reading process, only one event has to be in the memory. The burden placed on you as a designer of XML views is not very serious in the XML case. All you have to do is make sure that if there are two or more alternatives for the type of view that will be read, the first element in each alternative is different.
A note about error messages from the readers:
When you get an error message from the reader saying that an element X was
expected, keep in mind that when the reader framework has several options to
proceed, it will try them all before giving an error. So, for instance, if you
have an XML file that looks like this:
<phonebook>
<contact>...</contact>
<contact>...</contact>
<kontact>...</kontact>
<contact>...</contact>
</phonebook>
Then JXV would read the first two contacts, then upon seeing the <kontact>
element it would decide that there are no more elements in the contacts
collections. So it would look for what it expects to come after the contacts
collection XML view, which is </phonebook>. So the error message would say
"expected </phonebook>, got <kontact>". Try to remember
this when you see JXV's error messages.
There is no need to support a DOM-equivalent of View Reader, because any DOM tree can easily be transformed into a series of SAX events. From this you may also deduce that the DOM Views are also redundant. But this is not the case. JXV's DOM views are not mere static DOM trees, they are reflective. This means that when the underlying object model changes, the DOM view changes with it. It also means that the information is not duplicated in the tree: the tree queries that object model for the actual data whenever it needs it. The tree is also dynamic and doesn't have to be completely loaded in memory. Only the parts that are needed are loaded on demand. To improve performance, parts of the tree that are loaded are weakly-referenced so that if the GC doesn't need them, they can be reused the next time they are needed. Another feature of the reflectiveness of the tree is that every node in the tree can always be traced back to the object to whose view it belongs. If you are running XPath or XQuery expressions on the tree, this is very handy.
A note about the mismatch between XML
documents and
objects grpahs:
As I mentioned above, there is a basic mismatch between XML documents and object graphs
that causes some counter-intuitive results. For instance, consider a Person object. This
object has a list of PhoneNumbers each giving a single phone number of this
person. An XML file representing this model might look something like this:
<person>
<phone-number>
embed the view of the PhoneNumber
object here
</phone-number>
...
</person>
The <phone-number> element itself is not a part of the view of the
PhoneNumber view,
but rather a part of the view of the person. This is understandable. For
instance if the Person also had a "myPhoneNumber" property we might want a
<my-phone-number> elements in the person view to represent it. It doesn't make sense for this
element to be a part of the PhoneNumber view, because it depends on the name of
the property in which the PhoneNumber object appears, not the PhoneNumber object
itself.
Even though it makes sense for the <phone-number> element to be a part of the
Person view rather than the PhoneNumber view, it is counter-intuitive in some
contexts. A particularly important example is the evaluation of XPath
expressions. When you evaluate an expression like "//phone-number" you want to
choose all the PhoneNumber objects, not all the Person objects. So a method needs
to be devised to differentiate between the view that owns the node and the view
that is contained in the node. JXV does this by providing two methods in the
JXVNode interface: getOwnerView and getContainedView. Deciding what is the
contained view is a heuristic process, because a node is not guaranteed to
contain a view or to contain only one view. If the node does not contain exactly
one view, an exception is thrown.
It is also important to realize that some times it does make sense for the "main
element" to be part of the view of the contained object rather than the
containing object. This is often the case when dealing with class hierarchies
that use inheritance. For instance, suppose you have an Expression class and two
subclasses, AndExpression and OrExpression. You want the subclasses to be
represented by an <and> and <or> element, respectively. However, the class that
contains an expression can't know what type of expression it holds (assuming the
property type is the superclass, Expression), and therefore
can't decide how to name the element. So in this case the main element really is
the responsibility of the Expression itself, not the object that contains it. In
these cases, you would need to use getOwnerView rather than getContainedView
when you select an <and> or <or> element with an XPath expression.
The JXV configuration file has a pretty simple generic format, where each specific type of configuration uses it's own namespace to prevent collisions. This allows new View Factories and other components that extend JXV to pass on their configuration as a part of the configuration file. This is an important feature, because maintaining a different set of files of each component can quickly become a maintenance nightmare. It also eliminates the need of each view factory implementer to devise and implement his/her own configuration mechanism, making it easier to write JXV extensions.
The generic format is as follows: the root element has name <config:jxv-config> (the "config" prefix in this tutorial is assumed to be mapped to the JXV config URI, "http://jxv.sf.net/config"). This element can have a "config:priority" attribute specifying the default priority of config items in the file. If this attribute is missing, the value 50 is used by default. The element contents of the root element have a format called the "config context format". It has a separate name because it can also appear inside certain other elements. There are two types of children in this format:
The config context format can also appear as the content of certain types of elements, allowing you to "specialize" the main config and override some of it's definitions in certain contexts. When the Config Items appear as children of the config file's root element, they must have a config:category attribute. If they appear in an overriding context the attribute can be omitted, in which case the Config Item is assumed to be applicable to all classes. The config:priority attribute is always optional. If it does not appear, the file's default priority is used.
The last concept we will cover in this section is the concept of XML templates. Most of the major view factories (document, JavaBeans, array, Collection and Map views) use XML templates. As a JXV user, this means to you that all of these view factories are configured in a similar way. However, the concept of XML templates is far more important than just a configuration method. It is a simple and powerful abstraction for describing object-to-XML mappings. JXV has a package, org.jxv.template, for dealing with this abstraction. It allows users to read and write templates (in both SAX and DOM forms). In fact, this library is the only place in JXV where XML content other than Text nodes (SAX character events) is produced (for writing) or consumed (for reading). This means that new view factories can be developed almost without dealing directly with XML. If you ever write your own view factory, this is a big plus.
So, just what are XML templates? The basic idea is quite simple and consistent with the definition of JXV’s XML views. Here is an example of a template configuring the DOM view of a person:
The template can be instanciated to give an XML view (more accurately this is called an “XML fragment”, which is just like a view, only it doesn’t have to represent the view of one specific object; it is simply a set of attributes and a list of child nodes). The <bean:root> element itself is not a part of the instanciation. It is used merely as a convenient holder for attributes and child nodes. The attribute set of the instanciated template view will contain the “example-attr” attribute. The node list will contain exact copies of <bean:root>’s children, with one crucial difference: the “command nodes”, in this case <bean:property> are replaced with some other XML fragment. In this case, that XML fragment is the XML view of the value of the named property. The attribute set of this view is copied to the attributes of the parent (<first-name> or <another-element>), and the node list becomes a list of children in the parent.
The general concept of XML templates is just this, only instead of <bean:property> you can have some other set of command nodes. If a command node appears as a direct child of the root node, then it's attribute set is added to the attribute set of the template instanciation and it's list of nodes is added to the template instanciation's list of nodes. Otherwise, it's attribute set is copied to it's parent elements, and it's list of child nodes is added the the parent's list of child nodes.
There are two standard command nodes that are supported by all templates. These are used to create XML constructs that are otherwise impossible to create, because of the structure of XML. The first, <template:attr> is used to create an attribute whose content is specified by a command node. This is impossible without <template:attr> because attributes in XML only contain text, so they can't contain a command node. Here is an example of the use of the <template:attr> command node:
The XML view of the bean (i.e. the template instanciation) will contain an attribute called "size" whose value is the XML view of the "size" property. You should make sure that the DOM view of the property only contains text, because that's the only thing allowed inside an attribute value.
The second standard command node is <template:element>. This node can be used when you want the element name itself to be specified by a command node. If the name is static, don't use this element. Simply create an element with the desired name and place the command node inside it. Here is an example of using <template:element>:
The <name> and <content> elements are not themselves part of the template instanciation. The content of the <name> element (command nodes being replaced with their XML fragment) is used for the element's name. The content of the <content> element is used of the element's content. Attributes appearing in the <content> element become attributes of the created element. Again, you should make sure that the XML view of the property used for the name only contains text.
Note: the dynamically generated element name cannot contain a namespace prefix. If you want the element to have a namespace prefix, you can declare it in a "prefix" attribute inside the name element. The value of the "prefix" attribute must be a prefix with some previously declared namespace binding.
The PhoneBook object model is located in the test.phonebook package under the samples folder. Take a moment to look at the model. It is a very simple JavaBeans model with the following structure: PhoneBook is the main class. It has a title property and a contacts property which is a collection of Person objects. Each Person has some personal info (name, etc), home and work Address properties, a collection of emails (Strings) and a map of PhoneNumbers. The keys of the map are the type of number, e.g. work, home, mobile, etc. PhoneBookFactory is a simple factory class that creates random PhoneBook instances given three parameters: number of people in the book, max number of emails per person and max number of PhoneNumbers per person.
First let's run JXV on this object model without any configuration and see what happens:
Don't worry, you are supposed to get this error. Go to "c:\temp"
and check out the file that JXV created. It seems alright, but there are some
things that could be better. In the following sections you will see many configuration options that tweak the way the view will look. Which is
best for you depends on your own personal preferences and the requirements of
your project.
What we actually ran in this example is JXVTester, a command-line utility that
comes with the JXV distribution. Following is a description of this tool and
another similar tool. We will be using JXVTester throughout this tutorial, so
make sure you understand it's basic operation before proceeding.
The JXV package comes with a simple command-line tool, org.jxv.test.JXVTester, designed to help users try out JXV on their own object models without having to write JXV-related code. By implementing a simple factory interface, you can generate XML-views for your objects without studying the JXV API. Once you feel comfortable with JXV's features and configuration mechanisms, you should be able to start using JXV in your own code in a matter of minutes.
The basic functionality of JXVTester is:
Repeat for a specified number of iterations:
Construct an object using a factory.
Write it to the output folder.
Apply some XSLT transform to it and write the result to the output folder (optional).
Read the object back and compare it to the original (optional).
Write the object that was read to the output folder again, and compare the two files you wrote to make sure they are identical (optional).
As you can see, other than simply applying JXV to an object model and writing out an XML representation, JXVTester can also perform some level of integrity tests, such as making sure that the XML can be read back into an object. It also gives you some performance and timing diagnostics. When looking at those timing figures, keep in mind that the first iteration involves a lot of configuration-loading, cache-filling, etc., so it is usually a couple of seconds longer than the following iterations (even if the following iterations only take a couple of milliseconds). Therefore, in order to get reliable results about the timing you should use at least two iterations. It's generally a good idea to use even more, in order to see how memory usage and other factors affect the performance as the application progresses.
JXVTester's syntax is:
java org.jxv.test.JXVTester [-DOM] [-r|-rw] [-xslt file-name] [-configname config-name] output-dir factory-class iterations [factory args]
-DOM: specifies that objects will be written using the DOM view rather than the SAX view. This is usually slower when all you are doing is writing an XML file, so the default is to use a SAX view.
-r: specifies that the XML file that was written should also be read back. If this option is used, the tester will also try to compare the two objects (the original one and the read version) and indicate whether or not they were equal.
-rw: specifies that in addition to the effect of -r, the read object should also be written again, and compared to the first written file. This option provides the highest amount of checking on the consistency of the reading and writing.
-xslt file-name: specifies that the XML viw should be transformed using the XSLT template in file-name. The result is written to the output folder.
-configname config-name: specifies the document config name to use. If omitted the default config name is used.
output-dir: this is where output files are written.
factory-class: this is the class implementing org.jxv.test.ObjectFactory to return your object model.
iterations: the number of iterations to be performed. Each iterations fetches a new object from the factory, and writes (and reads and rewrites) it to the output folder as a separate file.
factory-args: additional arguments that are passed to the factory object.
If you're planning to use JXV for reading XML as well as writing, you might also be interested in testing how the JXV readers read files that you generate yourself, rather than just read files that JXV generated. In order to do that, a separate utility is provided:
The JXVConsoleReader is a console application which reads XML views of objects from XML files. It is capable of printing the XML view of the resulting object graph to a specified file or stdout (so that you can examine the result), and compare the resulting XML view with the original file. However, note that the comparison performed is byte-to-byte, so it's possible that equivalent XML documents won't match because of ignorable whitespaces and other physical differences. For more complex, "semantic" comparisons you should output the file and use an external diff util.
JXVConsoleReader's syntax is:
java org.jxv.test.JXVConsoleReader [-print output-file|"stdout"] [-compare] [-configname config-name] file class
-print: specifies that the resulting object should be written out, as an XML view, to the output file or stdout.
-compare: specifies that the resulting object's XML view should be compared to the original file.
-configname config-name: specifies the document config name to use. If omitted the default config name is used.
file: the name of the file to be read.
class: the class of the object whose XML view is in the specified file.
Now that we've seen the basic utilities we will use, we can start building a configuration file for our PhoneBook example.
The first thing we ought to do is fix that exception that is stopping us from
reading the file. The problem, as indicated in the error message, is in
test.phonebook.Country. This class is an implementation of a type-safe
enumeration. It's not a JavaBean. Specifically, it doesn't have a public
no-arguments constructor. As a result, when JXV tries to read it using the
default factory, the JavaBeans view factory, it gets an exception when it tries
to instanciate the class. What we will do is configure JXV to use the flat
factory instead, which is used to create "flat" text views for objects.
The default flat factory behavior is to use the toString() representation as the
text of the view, and to use a constructor with a single String argument to read
the view. Since our Country object doesn't have such a constructor, we will need
to define a marshaller that will do the reading/writing of Country objects.
Marshaller classes must extends the abstract org.jxv.flat.Marshaller class. The
Country class already has a static inner class called Marshaller that marshals
it. The marshaller class doesn't have to be an inner class, it is just a
convenient naming scheme. If you don't want to put JXV-related code in your
business objects you can put the marshaller in a separate class or even a separate
package.
Here is the new config file:
The first line, sais: "create a config item for the factory URI that would
match the test.phonebook.Country class and configure it to use the
org.jxv.JXVFlatFactory factory". The general format for factory config items is
no more complicated than this. The only attribute it defines is "factory-class",
which should be a name of a view factory class.
The second line sais "create a config item for the flat URI that would match
the test.phonebook.Country class and configure it to use the test.phonebook.Country$Marshaller
marshaller". Again the general format for the flat config is no more complicated
than the single required attribute "marshaller" that should contain the name of the marshaller class.
In order to be able to use the factory config item, you need to know what XML view factories there are. The following table summarizes information about the view factories available in the JXV distribution. More details are given in the sections that follow.
Factory class | Description |
---|---|
org.jxv.bean.JXVBeanFactory | A factory for generating XML views of JavaBeans. This is the default factory for most classes. |
org.jxv.container.JXVCollectionFactory | A factory for generating XML views of Collections. |
org.jxv.container.JXVArrayFactory | A factory for generating XML views of arrays. |
org.jxv.container.JXVMapFactory | A factory for generating XML views of Maps. |
org.jxv.flat.JXVFlatFactory | A factory for generating "flat" text views. |
org.jxv.TypeDecoratorFactory | A factory that decorates the view of the next configured factory with a "class" attribute. You only need to use this factory directly in extremely rare circumstances, as JXV usually configures it automatically where needed. |
A note about primitive types:
JXV is implemented using reflection. This means that primitive types are not
accessed directly: the reflection classes wrap and unwrap them into primitive
wrapper classes whenever required. Consequently, XML views are always created
for Objects, never for actual primitives. So when you are creating a config item for
the factory namespace or a config item for a view factory, and you want it to
apply to some primitive type, you need to put the wrapper type in the config
item's category, not
the primitive type itself.
What we have done in this configuration file is made the flat factory be the default factory for generating views of our Country class, and configured the flat factory to use our marshaller to read/write the text representation of the country. These two tasks make most of the work you do in configuring JXV: decide which factory will be used to create a view for an object, and separately configure that factory with it's own specific options.
Lets run JXVTester like we did last time, but this time tell it to use our new configuration file:
Notice the "-Dorg.jxv.config.config-files=..." in the command line. This is the part that passes the configuration file to JXV. We see that this time the tester did finish running without exception. As we've seen in the JXVTester section, what the tester does when given the "-rw" option is this: it writes the file, then reads it, then writes the object it read again. This way you can make sure that the read/write process preserves the structure of your XML. The tester also compares the object it read to the original version of your object.
In this case we see that the XML version of your original object was the same as the XML version of the object that was read from the file, and the objects matched (i.e. the equals() method return true). However, the objects are not exactly identical. The type of the collection in the original object were ArrayList, while the types in the read object are LinkedList. When JXV doesn't know the type of a Collection, it guesses (by default) that it is a LinkedList. Usually if JXV writes an object and sees that it's default guess (when reading) would be wrong, it writes a "class" attribute with the actual class of the object. However, this can only be done when the view of the object has "it's own" parent element, i.e. an element that contains no other views. If the view doesn't have it's own parent element, it can't write the class attribute because it would get mixed up with the class attributes of the other views in the same parent. In this example, the parent element of the contacts property is the "phonebook" element, which is also the parent of the "phonebook" view. This is because by default JXV inlines the elements of a collection directly in it's parent view rather than create a parent element that would contains all the elements. There are two ways to fix the problem. One is to simply give the collection it's own parent element. For instance we could have a "contacts" element that would contain the view of the collection. That way JXV could safely write the class attribute. The other option is to tell JXV that when it reads the contacts property, it should "guess" it has the type ArrayList rather than LinkedList. We will see how to do both these things in a later section, but first we need to go over the basics of bean configuration.
Here is a part of the XML generated by JXVTester with our new config file:
The first thing we will change is move the title to an attribute in the "phone-book" element. We've already seen something similar in the templates section, but there is one more factor at play here. We want to override the default configuration for the PhoneBook object, but we don't want to be forced to write all of it. Particularly, we don't want to touch the "contacts" property. The bean configuration has an element called "apply-defaults" that works like a macro, bringing in the default configuration for properties of a bean. If you write <apply-defaults apply-to="property"/> then the default configuration for "property" will be inserted. If you don't specify an "apply-to" attribute, then default configurations will be generated for all the properties that have not appeared in the configuration prior to the apply-defaults element. So, here is what we add to our configuration file:
First we create an attribute named "title" and put the view of the title
property in it. The command node <bean:property> represents the value of a
property of the bean. You can specify a "name" attribute with the name of the
property, or "getter" and "setter" attributes with the name of the getter method
and setter method of the propety. This allows you to read/write properties whose
names do not follow the standard JavaBeans naming conventions.
After creating the attribute with the view of the "title" property, <bean:apply-defaults> is used to include the configurations
for all the other properties. The title configuration won't be generated again,
because the title property has already appeared in the configuration. <bean:apply-defaults>
is not a command node. It doesn't have any implications at runtime. It's merely
a macro for generating configuration, and it is expanded when the configuration
is loaded. You can use it without fear of performance issues. You can use XSLT
templates to override the way apply-defaults generates default configurations
for properties, but since this is quite advanced and not commonly needed we'll
come back to it later.
If you want to skip a certain property, i.e. have apply-defaults ignore it,
without including it yourself in the config file, you can use <bean:skip
property="..."/>. The named property will be ignored by apply-defaults.
A note about apply-defaults and namespaces:
The elements generated by apply-defaults has the same namespace prefix and
URI as their parent element. If you want
apply-defaults to generate elements with a different prefix,
you can specify it in a "prefix" attribute in the <apply-defaults> element.
Attributes don't use namespace prefixes by default, but you can override this
behavior by specifying your own XSLT template for generating property
configurations as noted above.
Run JXVTester in the usual way, this time using config2.xml. Exam the
resulting XML file. You should now see a "title" attribute instead of the title
element we had before. Now we'll try something a little more complicated
involving both bean configuration and container configuration. "container" is a
configuration namespace that we use to configure the views of both Collections
and arrays.
Instead of having "email" elements right in the "contact" element, we will place
them in a separate "emails" element. We'll start by just putting the emails
property in it's own element like we did with the title element. Here is the
relevant config:
Run JXVTester with config3.xml and examine the XML it outputs. There are a couple of things to notice. First, we're suddenly getting an "item" element for each email instead of an "email" element. Second, now that the emails collection has it's own element, JXV can write it's runtime type in a class attribute so it can correctly read it. The objects still don't exactly match (although the equals() method does return true and so JXVTester prints they are identical), because the contacts property is still being read as a LinkedList instead of an array list. We'll fix that using a different technique later.
The reason we are getting that "item" element is that it is the default view for collections. The container itself doesn't know that it is the value of a property called "emails", and therefore it can't know what element name to use. Usually the default bean configuration passes this information to the container. I should point out at this point that if we replaced <bean:property name="emails"/> with <bean:apply-defaults apply-to="emails/> the problem would be solved because the apply-default mechanism would generate the correct configuration. But in order to learn how to configure collections and pass configuration data from one config item to another, we'll do it ourselves. Here is the correct configuration:
Run JXVTester with config4.xml and see how the "item" elements have now been replaced by "email" elements.
There are a couple of new things going on here. First of all, we see that the <bean:property> element now has children. This was briefly described in the introduction section as a way of "specializing" the main JXV configuration. Basically, <bean:property> or any other command node that represents the XML view of some object, can have children with the same format as the main JXV config (apart from the fact that the config:category attribute is optional, and if omitted the config item applies to all classes). When XML views are created for the objects represented by these command nodes, the config items that appear as children of the command node are consulted before the main configuration (unless the items in the main configuration has a higher priority value). So you can, in effect, override some of the configurations you have in your main config with special versions that will be used when evaluating a particular property.
Note: command nodes that represent the XML views of other objects are called "proxy command nodes" because they serve as proxies for other XML views. All proxy command nodes support the format described above. There is also a mechanism which generates default configurations for proxy nodes which is related to JXV's "guesses" of what types to read, and decisions of when to write "class" attributes. You can override this mechanism, like the mechanism for generating default property configuration in beans. Since this is advanced and not commonly needed, we'll touch that later.
The second new thing in this example is the container configuration. We create a config item for containers which will determine how containers format their views when generating views for the emails property. A container configuration is just like a bean configuration, only:
The explanation above should clarify what is going on. We configure the emails collection so that each item in it (i.e., each email) would instanciate the template, creating an "email" element containing the XML view of the email.
There is one more thing to change in the configuration of the emails
property. We will get rid of that "class" attribute that gets added to each item
in the collection. JXV has a pretty simple mechanism for deciding when to write
a class attribute. The config context that you have in every proxy command node
has a list of "substitute-reads", which determine which types of objects JXV
will try to read when reading the view of that command node. By default this
list is generated to contain a single "reasonable" value. For instance, in the
config context of a <bean:property> command node you would have a
substitute-read with the type of the property. In a <container:item> of an
array you would have the type of the array's component. When there is no
reasonable guess that JXV can make on it's own, for instance in a <container:item>
of a Collection, the substitute-read type is Object, but that is a mere
formality because the reader ignores substitute read types that are abstract
classes, interfaces, or java.lang.Object.
You can add your own list of substitute reads to a config context using the <reader:root>
element as shown in the following example. Your list is then added to the
default list: you don't have to repeat the default values.
When JXV writes an XML view as the value of a proxy command nodes, it checks if
the runtime type of the object is in the substitute-reads list for that node. If
it is, then the type would be tried by JXV anyway, so there's no reason to write
a "class" attribute. If it isn't, then a "class" attribute must be written,
otherwise JXV won't be able to decide which type to read.
A note about recursive expansion of substitute reads:
Substitute read lists are expanded recursively in the following way: for
each class in the substitute reads list, JXV looks up all the <reader:root>
config items whose category matches the class. The substitute-read lists from
all these items is added to the list of substitute-reads, and processed
recursively. JXV correctly resolves cycles involved in this recursive process.
You can use the recursive expansion to define global substitute reads for some
types. For instance, if you have an interface that you use in your project and
you have three implementation classes, you can define these classes as
substitute reads for the interface in the main configuration context. Then you
wouldn't have to define them separately each time you create a proxy command
whose base type is the interface. The base type would be included in the
substitute-reads list by default, and then expanded to include your three
implementation classes.
A note about specifying substitute reads with flat
views:
The JXV reader architecture has no way of knowing that a certain XML is not
a flat view, because even if there is no text, it could be a flat view with an
empty String. So, when you define a substitute-read of a text view, be sure that
it the actual runtime type of the object. Otherwise JXV would read an incorrect
type. If you can't know the actual runtime type of the object, let JXV write a
"class" attribute.
Now we will configure the <container:item> command node with the substitute-read "java.lang.String":
Run JXVTester with config5.xml. In the XML output you should see that the <email> element now has no "class" attribute. JXV knows the type from the substitute read we have defined, so it doesn't have to write it as an attribute.
We will now use substitute-reads again, this time to preserve the runtime type of the contacts property. You may remember the "class" attribute doesn't get written because the contacts XML view doesn't have "it's own" parent, and it's class attribute could be confused with the class attribute of the phonebook itself. We have seen one solution to this type of problem already: putting the collection in it's own parent element. The other solution, the one we will use now, is to specify with substitute-reads which type of collection JXV should read. Here is the new configuration for the PhoneBook bean:
Run JXVTester with config6.xml. You can see the <contact> elements now have no class attribute, because we have specified their class in the substitute-reads. Also, the objects read by the JXVTester are now truly identical to the originals, now that we've taken care of both "problematic" collection properties. There's not much new in this example. Notice how we pass more than one config item in the <bean:property> element. We pass one config item for specifying the class that should be used when reading the property, and another config item for configuring the view of the container.
We will now take a look at the map config item. It is used to configure XML views for Maps. The general format is very similar to that of container configurations, the only difference being that the <container:item> command node is replaced by <map:key> and <map:value>, representing the XML views of the key and value of a Map entry, respectively. These command nodes are proxy command nodes as well.
As you can see in JXVTester's XML output, the default format for a map includes a "key" element containing the key, followed by the XML view of the value. We will override this default and configure the phoneNumbers attribute of the Person bean to generate for each entry a <phone-number> element with a "location" attribute containing the view of the key, and children containing the view of the PhoneNumber object. There is nothing particularly new or complicated about this config. It's just a combination of some of the stuff we've seen. Here is the new config item for person (the emails property config is unchanged):
Run JXVTester with config7.xml. The new part is the phoneNumbers property config. We configure the map view factory to create a <phone-number> element with the relevant information for each entry in the map. Also notice that we define substitute reads for both the value and the key so that the XML doesn't get "polluted" with "class" attributes. That's about as complicated as map configurations get.
If you've understood all the configurations demonstrated up to this point,
you should know which factory generates each XML node (i.e. elements,
attributes, and text) except for one node. The only thing we haven't gone over
yet is who generates the root element, the "phonebook" element. This is not a
part of the XML view of the phone book itself. The factory that generates this
element is the document factory. This is the last major view factory you need to
know of. It has a fairly simple configuration. All it does is act as a wrapper
around a normal XML view, wrapping it in an XML document.
Like the bean configuration, a document also generates a default config item for
a class if you don't define one yourself. The default configuration consists of
just an element with the name of the class that a default configuration is being
created for (the name is converted to lower-case words with hyphens like bean
property names). Inside this element the default XML view of the object is
inserted.
You can write your own document config if you don't like the default one. The config format is just an XML template, like the bean config format, with the proxy command node being <document:content>. Here is an example that replaces the <phone-book> root element with <book>:
You can also specify a config name in a "document:name" category. When you programmatically create a JXV document view (or reader) you can specify an optional name parameter, which will be used to chose the particular document config you want. Run JXVTester with config8.xml to see the effects of this configuration item.
We have seen all the basic types of configurations available in JXV. In the next section we will cover some more advanced topics: integrating namespaces, overriding the bean property configuration generation, and some other advanced examples. You can go on reading these topics, and they would certainly enable you to use JXV more flexibly and efficiently. However, they are not mandatory for writing most applications. They may come in handy, but you can do without them. So if you are in a hurry, feel free to skip the next configuration sections and come back to it later.
In this section we will learn how to incorporate namespaces into JXV's XML views. Namespaces generally interact smoothly with the notion of templates. All you have to do in order to create an element with some namespace is make the template element have that namespace. However, there are a couple of questions: who creates the actual namespace declaration? how do the default configuration mechanisms interact with namespaces? how do template command nodes create attributes and elements with namespaces? This section addresses these questions and, as usual, demonstrates a configuration file using the new techniques.
JXV doesn't create namespace declaration attributes on it's own. You need to
declare them, before you use them, as normal attributes in a template. That way
the resulting XML view would also contain the declarations.
The default configuration mechanisms generally follow a simple inheritance
scheme: elements that are generated by default would have the same prefix and
namespace as their parent node. <bean:apply-defaults> allows to to override this
behavior by specifying a "prefix" attribute that would determine which prefix
would be used for the generated elements. Attributes are generally generated
with no namespace, although you can override this.
The template command nodes <template:attr> and <template:element> require
special treatment because their namespace allways has to be the template
namespace. In <template:attr>, you can simply specify a QName as the value of
the name attribute: that is, the name of the attribute can contain a namespace
prefix. In the <template:element> case this is impossible because the name is
deduced at runtime. Therefore you may specify a "prefix" attribute in the <name>
element specifying a namespace prefix.
In the following configuration (based on config8.xml) we have made two changes:
You can read the entire configuration in config9.xml. Run JXVTester on it and observe the results.
This section is an example of using the <template:element> command node. We will replace our current map configuration with a configuration where each map entry creates an element with the name of the location, whose children are the view of the value. Here is the relevant part (this is only the part that configures the phoneNumbers property: for the entire configuration se config10.xml):
Run JXVTester with config10.xml and see how now we have elements with names
matching the type of location the map entry represents.
Most of this new configuration is pretty simple. However, there is one important thing to
notice. We had to enclose the property in a <phone-numbers> element, otherwise
reading the XML view would fail. If the after reading all the phone numbers JXV
would get to the "first-name" element, it won't know that this is not another
element with a runtime-determined name. After all, we could have a key in the hashmap whose value is "first-name". So if you have a context where you have a
variable number of "dynamic" elements, you have to give JXV some means for
detecting when they end. If you have just one (or any statically defined number
of) dynamic element this problem does not arise because JXV simply reads that
many elements.
We have seen in the previous sections that when there is no explicit configuration given for some property, the bean factory generates one. The mechanism that generates properties and also be invoked directly through <bean:apply-defaults>. In the section we will se how the default configurations are actually generated, and how you can change the way JXV generates default configurations.
Default configurations are created by running an XSLT template on an XML document of the following format:
The name-noun and name-noun-detected elements are designed for use by
container configurations. They allow the container configuration template to
display each item in a "contacts" collection as "contact".
The prefix and namespace elements specify the prefix and namespace that the
template should use in the XML it generates. The coloned-prefix element is
merely a convenience. You will see how it is used in the next example.
To define a template, you create a config item with the "beandef" (i.e., bean
default) namespace. The category of the item is matched against the property's
type. The template should have one child which is the root element of an XSLT
template. When generating a default configuration for a property, JXV would
create a document with the format above and feed it to your template. Like in
JXV's XML templates, the root element of the result is actually ignored. It
merely used as a holder for attributes and a list of elements. The attributes of
the root element of the result are copied to the bean configuration, and the
children of the root element of the result are inserted into the bean
configuration, where the <bean:apply-templates> element appeared. If the <bean:apply-templates>
element generates more than one property configuration, the process described
above is repeated for each property, in alphabetical order.
There is one more thing. When you use namespaces like "bean" and "container" in
your XSLT templates, the XSLT processor adds namespace declarations for these
namespaces in the root element. To avoid getting these namespace declarations
copied into the XML view, you can define a list of "excluded-prefixes" in the
config item. The list is a space-separated list of prefixes. All the namespace
declarations for prefixes that appear in the list are not copied to the XML
view.
A note about primitives:
I've mentioned in a note before that when you create config items for view
factories and you want them to apply to primitives, you should make them apply
to the primitive wrapper class instead, because JXV always creates views for
objects, it never accesses primitives directly.
In the case of the beandef config item, this is not the case. Here the actual
type of the property is used, and that type may very well be a primitive type.
Keep that in mind when writing your own beandef templates.
Before we write our own template, let's see an example from JXV's standard configuration files:
Let's go over what this template does. If the name-noun-detected element is true, it generates a child element of this format:
Where name-noun is the noun form of the name constructed by JXV. We know from
the container configuration section that this configuration would create for
each item in the container a "name-noun" element containing the view of the
item.
The other case is simpler. If the name-noun was not detected, the child element
has this format:
This creates an element with the formatted name of the property, containing the view of the property. The view would be formatted using the default configuration: an "item" element for each item in the collection (with the parent's namespace and prefix).
Now we are ready to write our own template. We will write a template that formats all String properties as attributes instead of elements. Here is the relevant part of the configuration file:
Run JXVTester with config11.xml and examine the results. Notice that here we
are generating attributes without namespaces prefixes. This is quite standard in
many XML formats. Elements have namespaces but their attributes do not. Anyway,
if you want to generate attributes with namespace prefixes all you need to do is
replace <template:attr name="{/property/formatted-name}"> with <template:attr
name="{/property/coloned-prefix}{/property/formatted-name}"
namespace="{/property/namespace}">.
Notice the xmlns="" namespace declaration we have in the beandef config item. If
we didn't have it, the <root> element would have the default namespace, which is
"www.phonebook.org". That would cause the
XSLT processor to add a declaration for this default namespace in the result,
and that declaration would have been copied (as an attribute of the root
element) to the <map:work-address> and <map:home-address> elements. This doesn't
really cause any harm, it just re-declares a namespace that is already declared
in the scope. Even so, I think it's better to avoid such redundant declarations.
In the paragraph above we have seen how the XSLT processor might produce namespace declarations that we didn't intend for it to create. Another case where this happens is when your template generates elements or attributes with the namespace specified in /property/namespace. You can't specify the prefix in the exclude-prefixes attribute, because the prefix itself is determined at runtime. JXV solves this by removing namespace declarations of the default namespace with the default prefix. So you don't have to worry about getting rid of these declarations.
There are similar mechanisms for generating the default configurations of proxy command nodes and some other types of configurations. However, because you only need to change those in very rare cases, they are not described in this tutorial at this point. If you still want to learn about these mechanisms, please consult the mailings lists or forums in JXV's website.
That's it for the PhoneBook example. I highly recommend you try to modify the configuration files and/or the code and see what happens. If you come across something you can't figure out, drop a message in the website. Here are some ideas for further improvement:
The JXVTester tool is nice for practice, but if you want to actually use JXV in your own applications you have to know how to call it from your own code. It may seem weird that this section appears so late in the tutorial. After all, using JXV in your own applications is what this tutorial is all about, right? Fortunately, there isn't a lot to learn here, at least unless you're planning to write your own view factories or extend JXV. After all, most of what JXV provides is just implementations of standard APIs like DOM and SAX. If I had to explain all of these APIs here it would take a while. But given that they are standard and you probably know them already, all you have to learn is how to "hook" JXV into your code. In all the examples below you should import the relevant classes from org.jxv.document and org.jxv.config.
A JXV configuration is represented by the org.jxv.config.JXVConfig class. You can load the "system config", the config based on the org.jxv.config.config-files environment variable, using the JXVConfig.getSystemConfig() method.
Your main entry point into JXV would usually be the org.jxv.document package. This package provides classes for creating DOM documents, SAX sources, and XML readers for your objects. A few simple examples follow. In these examples I only pass an "object" parameter, but the methods and constructors are overloaded and can also take a JXVConfig instance (if omitted they use the system config) and a config name (if omitted they use the default name, an empty string).
To get a Document for a specified object use the following line:
Document doc = new DocumentDOMView(object);
All the nodes in a DOM document created by JXV implement the org.jxv.dom.JXVNode interface. This interface has two method for getting the contained view and the owner view of a node. From the view you can get the associated object, and thus map a node back to it's object. The test.xpath package contains a command-line utility called XPathRunner which takes an ObjectFactory (like JXVTester), creates an object, and allows you to run XPath queries on it and get back objects. This example makes use of the XPathAPI class in the "org.apache.xpath" package. Converting it to use your favorite XPath engine should be quite simple.
To get a SAXSource instance use:
SAXSource src =
DocumentSAXView.getInstance(object.getClass()).getSAXSource(object);
First, we get a document SAX view for the desired class. SAX views, unlike
DOM views, are instaciated once-per-class because of their stateless nature. To
get a SAX source containing the the view of a specific object, we use the
getSAXSource(object) method. SAXSource is a standard interface defined in
the Java API for XML Processing. See the JAXP documentation for information on
how to use it.
Note that a SAX view (the result of
DocumentSAXView.getInstance(object.getClass()))
supports multithreading.
Finally, to read object views for object of a specified class use:
reader = new DocumentReader(clazz);
return org.jxv.reader.JXVReaderHelper.read(myInputSource, reader);
Note that of all the objects we create in this section, readers are the most "heavyweight". A reader is not multi-threaded, but it can be reused. If you plan to read more than one XML file, you should strongly consider caching the reader.
You can look at the source of org.jxv.test.JXVTester and org.jxv.test.JXVConsoleReader for working examples of this code. The "Boolean Formula Satisfier" example briefly described below demonstrates the manual creation of JXVConfig instances.
This example focuses mostly on the concepts you have seen in the substitute-reads section. It reads boolean formula expressions to from XML files, and tries to find some assignment of variables that makes the expression true. The main focus is on using substitute reads to read the expression fields (which usually have the type BooleanExpression, the superclass of all expression classes).
The other thing we see in this example is how to instanciate a JXV configuration object and load definitions into it. In the PhoneBook example we used JXVTester, which uses the system configuration. However, sometimes you may want to create your own configuration instance and load definitions into it yourself rather than rely on the "org.jxv.config.config-files" environment variable. For instance, in an EJB application or Servlet you may not feel comfortable playing with the start-up scripts of your application server to pass the environment variable. Also, there is some level of mutual-blocking between multiple threads using the same JXVConfig (although it is kept to a minimum). If you discover that lock congestion is slowing down your application, you might want to load several JXVConfig instances and use a different one for each thread. Note however that these instances do tend to be somewhat heavy, because JXV does a lot of caching at various levels to improve performance.
Here is the relevant piece of code from Satisfier:
Once you instanciate a JXVConfig instance, there are two common things you can do with it:
Note that in order two perform these two operations you first need to access the base context using getBaseContext(). This is the actual implementation of a config context (e.g., it is also used for config contexts of proxy command nodes).
There are some other options, you can look them up in the API. A bit further down the code you can see how this configuration object can be passed to the document reader class as an additional argument to the constructor.
Because this example builds it's own configuration object, you don't have to pass it any environment variables. Run this example on the sample expressions in the "samples" folder like this:
java -classpath .;../lib/jxv.jar test.bool.Satisfier test/bool/exp<X>.xml
The output is a variable assignment that satisfies the expression, or a message stating that the expression is unsatisfiable. Note that exp3.xml is intended to be malformed, the exception that is thrown is not a bug.
This is another big, fully configured example. The classes and config files are available in the test.order package in the samples folder. If you want some more practice, trying reading the configuration files. They are documented with comments inside the XML to make this easier.
This example is the last, and also the simplest. In fact, it doesn't use any configuration files at all. I included it in the distribution mainly to remind you that despite all of the elaborate configuration options shown in this tutorial, JXV doesn't really require any configuration to work. Run this example using JXVTester like this (from the samples folder):
java -classpath .;../lib/jxv.jar org.jxv.test.JXVTester -rw c:\temp test.sales.SalesFactory 1 5 5
The resulting XML is exactly what you would expect. If you don't have any specific XML format in mind, this XML view is probably as good as any.
This tutorial walked you through the important aspects of JXV. I hope it provided a solid introduction, at least for those users who want to use JXV (rather than extend it). While future versions of this tutorial may cover other features, you don't have to wait. The best way to truly understand JXV is to read the sources. They are freely available and come with every distribution.
I would be glad to hear your experiences with JXV in general and this tutorial in particular. The homepage at http://jxv.sourceforge.net contains links to mailing lists and other contacts where you can submit your comments. All comments are appreciated.
Class categories are used in the JXV configuration system to denote the sets of classes a particular config item applies to. In order to allow you to flexibly and easily type in these categories, JXV implements an expression language for describing sets of classes. The components of this expression language are:
Parentheses can be used in expressions for grouping. Whitespaces are ignored. Any category may be preceded by a string of the from "#id#". This associates the category with the specified id, so it can be addressed using ref(id). Any identifier with a form different than the ones above is assumed be be a class name, and denotes the class itself.
Here are some examples of class category expressions, and their meaning:
a.** & ~(a.b.*): meaning any class in a
sub-package of a, except for classes in package a.b.
array(subclass(java.util.Collection | java.util.Map)):
an array of classes that are subclasses of Collection or Map.
#array-id#(java.util.Collection | array(ref(array-id))):
java.util.Collection, java.util.Collection[], java.util.Collection[][], ...
The grammar of the expression language is as follows: