Hi Robert,
One way I started using betwixt is to allow our testing organization to easily create
XML test data (they can modify the XML values to create a large test bed without
writting a lot of java code). One thing they asked for was a way to include XML files
for some pieces of data. I originally fought with finding a way to create a custom
digester rule to do that, but then realized it could be done relatively easily with a
custom BeanCreator.
Below is a "IncludeBeanCreator" along with a test case for it. If you think it would
be useful for others, you can include it in Betwixt (I still haven't dealt with
looping includes, but I think I could add that relatively quickly when I get some
additional time to look at it).
-Brian
IncludeBeanCreator.java
------------------------------------
package org.apache.commons.betwixt.io.read;
import org.apache.commons.betwixt.io.BeanReader;
import org.apache.commons.betwixt.BindingConfiguration;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import java.net.URL;
import java.io.IOException;
import java.beans.IntrospectionException;
import java.util.List;
/**
* BeanCreator that allows for "include" files. The current element's "include-file"
property
* indicates the name of the XML file that should be processed. For example, the
following XML indicates
* that the "venue" property should be created by parsing the file
"org/apache/commons/betwixt/io/read/venue.xml":
* <p/>
* <p/>
* <PRE>
* <?xml version="1.0"?>
* <PartyBean>
* <dateOfParty>Wed Apr 14 19:46:55 MDT 2004</dateOfParty>
* <excuse>tired</excuse>
* <fromHour>2</fromHour>
* <venue include-file="org/apache/commons/betwixt/io/read/venue.xml"/>
* </PartyBean>
* </PRE>
* <p/>
* The venue.xml file would be a standard XML input for betwixt:
* <p/>
* <PRE>
* <?xml version="1.0"?>
* <venue>
* <city>San Francisco</city>
* <code>94404</code>
* <country>USA</country>
* <street>123 Here</street>
* </venue>
* </PRE>
* <p/>
* <p/>
* The include file is loaded as a resource using the current <code>ClassLoader</code>
following the rules
* as described by the <code>ClassLoader.getResource</code> method.
* <p/>
* The include file is parsed using a <code>BeanReader</code> with the
<code>BindingConfiguration</code>
* and <code>ReadConfiguration</code> which can be explicitly set, or if it is not set,
* a default will be used.
* <p/>
* The current class (obtained from the <code>ElementMapping</code>) will be
registered with the <code>BeanReader</code>
* using the "name" of the current <code>ElementMapping</code>.
*
* @author Brian Pugh
*/
public class IncludeBeanCreator implements ChainedBeanCreator {
private BindingConfiguration bindingConfiguration;
private ReadConfiguration readConfiguration;
private List parsedFiles;
/**
* Constructs a new IncludeBeanCreator.
*/
public IncludeBeanCreator() {
}
/**
* Constructs a new IncludeBeanCreator.
*
* @param bindingConfiguration the <code>BindingConfiguration</code> that will be
used to parse include files.
*/
public IncludeBeanCreator(BindingConfiguration bindingConfiguration,
ReadConfiguration readConfiguration) {
setBindingConfiguration(bindingConfiguration);
setReadConfiguration(readConfiguration);
}
/**
* Get the <code>BindingConfiguration</code> that will be used to parse include
files.
*
* @return the <code>BindingConfiguration</code> that will be used to parse include
files.
*/
public BindingConfiguration getBindingConfiguration() {
return bindingConfiguration;
}
/**
* Set the <code>BindingConfiguration</code> that will be used to parse include
files.
*
* @param bindingConfiguration the <code>BindingConfiguration</code> that will be
used to parse include files.
*/
public void setBindingConfiguration(BindingConfiguration bindingConfiguration) {
this.bindingConfiguration = bindingConfiguration;
}
/**
* Get the <code>ReadConfiguration</code> that will be used to parse include files.
* @return the <code>ReadConfiguration</code> that will be used to parse include
files.
*/
public ReadConfiguration getReadConfiguration() {
return readConfiguration;
}
/**
* Set the <code>ReadConfiguration</code> that will be used to parse include files.
* @param readConfiguration the <code>ReadConfiguration</code> that will be used to
parse include files.
*/
public void setReadConfiguration(ReadConfiguration readConfiguration) {
this.readConfiguration = readConfiguration;
}
/**
* If the "include-file" attribute is found, the bean is created by parsing the
include xml file.
* Otherwise bean creation is delegated to the other members of the chain.
*
* @param elementMapping specifies the mapping between the type and element.
* @param context the context in which this converision happens, not null
* @param chain not null
* @return the Object created, possibly null
*/
public Object create(ElementMapping elementMapping, ReadContext context,
BeanCreationChain chain) {
Attributes attributes = elementMapping.getAttributes();
String file = attributes.getValue("include-file");
if (file != null && !file.equals("")) {
BeanReader beanReader = new BeanReader();
if (bindingConfiguration != null) {
beanReader.setBindingConfiguration(bindingConfiguration);
}
if (readConfiguration != null) {
beanReader.setReadConfiguration(readConfiguration);
}
String name = elementMapping.getName();
Class clazz = elementMapping.getType();
ClassLoader loader = getClass().getClassLoader();
URL url = loader.getResource(file);
if (url == null) {
url = loader.getResource("/" + file);
}
if (url == null) {
throw new RuntimeException("Unable to locate include file: " + file);
}
try {
beanReader.registerBeanClass(name, clazz);
return beanReader.parse(url.toString());
}
catch (IntrospectionException e) {
throw new RuntimeException("Unable to register class with beanReader.
Classname: " + clazz, e);
}
catch (IOException e) {
throw new RuntimeException("Unable to process include file: " + file, e);
}
catch (SAXException e) {
throw new RuntimeException("Unable to process include file: " + file, e);
}
}
return chain.create(elementMapping, context);
}
}
TestInclude.java
----------------------------------------------------------------------
package org.apache.commons.betwixt.io.read;
import junit.framework.TestCase;
import org.apache.commons.betwixt.PartyBean;
import org.apache.commons.betwixt.io.BeanReader;
import java.net.URL;
import java.util.Date;
/**
* Test the IncludeBeanCreator.
*
* @author Brian Pugh
*/
public class TestInclude extends TestCase {
/**
* Test IncludeBeanCreator.
*
* @throws Exception if test fails.
*/
public void testInclude() throws Exception {
URL url =
getClass().getClassLoader().getResource("org/apache/commons/betwixt/io/read/party.xml");
assertNotNull("couldn't load party xml file!", url);
BeanReader beanReader = new BeanReader();
beanReader.registerBeanClass(PartyBean.class);
BeanCreationList chain = BeanCreationList.createStandardChain();
//put in second place (let the idref creator run first)
chain.insertBeanCreator(2, new
IncludeBeanCreator(beanReader.getBindingConfiguration(),
beanReader.getReadConfiguration()));
beanReader.getReadConfiguration().setBeanCreationChain(chain);
PartyBean result = (PartyBean)beanReader.parse(url.toString());
assertNotNull("Couldn't read a PartyBean!", result);
assertEquals("didn't get a Date right!", new Date("Wed Apr 14 19:46:55 MDT 2004"),
result.getDateOfParty());
assertEquals("didn't get excuse right!", "tired", result.getExcuse());
assertEquals("didn't get fromhour right!", 2, result.getFromHour());
assertNotNull("didn't get venue back!", result.getVenue());
assertEquals("didn't get venue city right!", "San Francisco",
result.getVenue().getCity());
assertEquals("didn't get venue code right!", "94404", result.getVenue().getCode());
assertEquals("didn't get venue country right!", "USA",
result.getVenue().getCountry());
assertEquals("didn't get venue street right!", "123 Here",
result.getVenue().getStreet());
}
}
XML files (n the org.apache.commons.betwixt.io.read package)
party.xml
----------------------------------------------------------------
<?xml version="1.0"?>
<PartyBean>
<dateOfParty>Wed Apr 14 19:46:55 MDT 2004</dateOfParty>
<excuse>tired</excuse>
<fromHour>2</fromHour>
<venue include-file="org/apache/commons/betwixt/io/read/venue.xml"/>
</PartyBean>
venue.xml
--------------------------------------------------------------
<?xml version="1.0"?>
<venue>
<city>San Francisco</city>
<code>94404</code>
<country>USA</country>
<street>123 Here</street>
</venue>
---------------------------------
Do you Yahoo!?
Yahoo! Tax Center - File online by April 15th