/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.xml.test;
import junit.framework.TestCase;
import org.eclipse.xsd.XSDSchema;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.defaults.DefaultPicoContainer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.helpers.NamespaceSupport;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.geotools.xml.Binding;
import org.geotools.xml.Configuration;
import org.geotools.xml.DOMParser;
import org.geotools.xml.Encoder;
import org.geotools.xml.SchemaIndex;
import org.geotools.xml.XSD;
import org.geotools.xml.impl.BindingFactoryImpl;
import org.geotools.xml.impl.BindingLoader;
import org.geotools.xml.impl.BindingWalkerFactoryImpl;
import org.geotools.xml.impl.NamespaceSupportWrapper;
import org.geotools.xml.impl.SchemaIndexImpl;
/**
* Abstract test class to be used to unit test bindings.
* <p>
* Subclasses must implement the {@link #createConfiguration()} method. It must
* return a new instance of {@link Configuration}. Example:
*
* <pre>
* <code>
* public MailTypeBindingTest extends XMLTestSupport {
*
* protected Configuration createConfiguration() {
* return new MLConfiguration();
* }
* }
* </code>
* </pre>
*
* </p>
* <p>
* The {@link #parse()} method is used to test binding parsing. Subclasses
* should call this from test methods after building up an instance document
* with {@link #document}. Example
*
* <pre>
* <code>
* public void testParsing() throws Exception {
* //build up an instance document
*
* //the root element
* Element mailElement = document.createElementNS( ML.NAMESPACE, "mail" );
* document.appendChild( mailElement );
*
* mailElement.setAttribute( "id", "someId" );
* ....
*
* //call parse
* Mail mail = (Mail) parse();
*
* //make assertions
* assertEquals( "someId", mail.getId() );
* }
* </code>
* </pre>
* </p>
*
* <p>
* The {@link #encode(Object, QName)} method is used to test binding encoding.
* Subclasses should call this method from test methods after creating an
* object to be encoded. Example:
*
* <pre>
* <code>
* public void testEncoding() throws Exception {
* //create the mail object
* Mail mail = new Mail( "someId" );
* mail.setEnvelope( ... );
* ....
*
* //call encode
* Document document = encode( mail, new QName( ML.NAMESPACE, "mail" );
*
* //make assertions
* assertEquals( "mail", document.getDocumentElement().getNodeName() );
* assertEquals( "someId", document.getDocumentElement().getAttribute( "id" ) );
* }
* </code>
* </pre>
* </p>
*
* <p>
* The {@link #binding(QName)} method is used to obtain an instance of a
* particular binding. Subclasses should call this method to assert other
* properties of the binding, such as type mapping and execution mode. Example:
*
* <pre>
* <code>
* public void testType() {
* //get an instance of the binding
* Binding binding = binding( new QName( ML.NAMESPACE, "MailType" ) );
*
* //make assertions
* assertEquals( Mail.class, binding.getType() );
* }
*
* public void testExecutionMode() {
* //get an instance of the binding
* Binding binding = binding( new QName( ML.NAMESPACE, "MailType" ) );
*
* //make assertions
* assertEquals( Binding.OVERRIDE, binding.getExecutionMode() );
* }
* </code>
* </pre>
* </p>
*
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*
*
* @source $URL$
*/
public abstract class XMLTestSupport extends TestCase {
/**
* Logging instance
*/
protected static Logger logger = org.geotools.util.logging.Logging.getLogger(
"org.geotools.xml.test");
/**
* the instance document
*/
protected Document document;
/**
* additional namespace mappings
*/
protected HashMap namespaceMappings;
/**
* Creates an empty xml document.
*/
protected void setUp() throws Exception {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
document = docFactory.newDocumentBuilder().newDocument();
namespaceMappings = new HashMap();
}
/**
* Template method for subclasses to register namespace mappings on the
* root element of an instance document.
* <p>
* Namespace mappings should be set as follows:
* <pre>
* <code>
* root.setAttribute( "xmlns:gml", http://www.opengis.net/gml" );
* </code>
* </pre>
* </p>
* <p>
* Subclasses of this method should register the default namespace, the
* default namesapce is the one returned by the configuration.
* </p>
* <p>
* This method is intended to be extended or overiden. This implementation
* registers the <code>xsi,http://www.w3.org/2001/XMLSchema-instance</code>
* namespace.
* </p>
* @param root The root node of the instance document.
* @deprecated use {@link #registerNamespaceMapping(String, String)}
*
*/
protected void registerNamespaces(Element root) {
root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
}
/**
* Registers a namespace mapping.
* <p>
* This mapping will be included in the "namespace context" of both the
* parser and the encoder.
* </p>
* @param prefix The prefix of the namespace, not <code>null</code>.
* @param uri The uri of the namespace, not <code>null</code>.
*/
protected void registerNamespaceMapping( String prefix, String uri ) {
namespaceMappings.put( prefix, uri );
}
/**
* Tempalte method for subclasses to create the configuration to be used by
* the parser.
*
* @return A parser configuration.
*/
protected abstract Configuration createConfiguration();
/**
* Parses the build document, explicity specifying the type of the root
* element.
* <p>
* This method should be called after building the entire document.
*</p>
* @param type The name of the type of the root element of the build document.
*/
protected Object parse(QName type) throws Exception {
Element root = document.getDocumentElement();
if (root == null) {
throw new IllegalStateException("Document has no root element");
}
Configuration config = createConfiguration();
if (type != null) {
config.getContext().registerComponentInstance("http://geotools.org/typeDefinition", type);
}
//register additional namespaces
registerNamespaces(root);
for ( Iterator e = namespaceMappings.entrySet().iterator(); e.hasNext(); ) {
Map.Entry mapping = (Map.Entry) e.next();
String prefix = (String) mapping.getKey();
String uri = (String) mapping.getValue();
root.setAttribute("xmlns:" + prefix, uri );
}
//default
root.setAttribute("xsi:schemaLocation",
config.getNamespaceURI() + " " + config.getSchemaFileURL());
DOMParser parser = new DOMParser(config, document);
return parser.parse();
}
/**
* Parses the built document.
* <p>
* This method should be called after building the entire document.
*</p>
*/
protected Object parse() throws Exception {
return parse(null);
}
/**
* Encodes an object, element name pair explicitly specifying the type of
* the root element.
*
* @param object The object to encode.
* @param element The name of the element to encode.
* @param type The type of the element
*
* @return The object encoded.
* @throws Exception
*/
protected Document encode(Object object, QName element, QName type)
throws Exception {
Configuration configuration = createConfiguration();
if (type != null) {
//set the hint
configuration.getContext()
.registerComponentInstance("http://geotools.org/typeDefinition", type);
}
XSDSchema schema = configuration.getXSD().getSchema();
Encoder encoder = new Encoder(configuration, schema);
//additional namespaces
for ( Iterator e = namespaceMappings.entrySet().iterator(); e.hasNext(); ) {
Map.Entry mapping = (Map.Entry) e.next();
String prefix = (String) mapping.getKey();
String uri = (String) mapping.getValue();
encoder.getNamespaces().declarePrefix( prefix, uri );
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
encoder.write(object, element, output);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
return dbf.newDocumentBuilder().parse(new ByteArrayInputStream(output.toByteArray()));
}
/**
* Encodes an object, element name pair.
*
* @param object The object to encode.
* @param element The name of the element to encode.
*
* @return The object encoded.
* @throws Exception
*/
protected Document encode(Object object, QName element)
throws Exception {
return encode(object, element, null);
}
/**
* Convenience method to dump the contents of the document to stdout.
*
*
*/
protected void print(Node dom) throws Exception {
TransformerFactory txFactory = TransformerFactory.newInstance();
Transformer tx = txFactory.newTransformer();
tx.setOutputProperty(OutputKeys.INDENT, "yes");
tx.transform(new DOMSource(dom), new StreamResult(System.out));
}
/**
* Convenience method for obtaining an instance of a binding.
*
* @param name The qualified name of the element,attribute,or type the
* binding "binds" to, the key of the binding in the container.
*
* @return The binding.
*/
protected Binding binding(QName name) {
Configuration configuration = createConfiguration();
//create the context
MutablePicoContainer context = new DefaultPicoContainer();
context = configuration.setupContext(context);
//create the binding container
Map bindings = configuration.setupBindings();
BindingLoader bindingLoader = new BindingLoader(bindings);
// MutablePicoContainer container = bindingLoader.getContainer();
// container = configuration.setupBindings(container);
// bindingLoader.setContainer(container);
//register cmponents available to bindings at runtime
context.registerComponentInstance(new BindingFactoryImpl(bindingLoader));
//binding walker support
context.registerComponentInstance(new BindingWalkerFactoryImpl(bindingLoader, context));
//logger
context.registerComponentInstance(logger);
//setup the namespace support
NamespaceSupport namespaces = new NamespaceSupport();
HashMap mappings = new HashMap();
try {
for (Iterator d = configuration.getXSD().getDependencies().iterator(); d.hasNext();) {
XSD xsd = (XSD) d.next();
XSDSchema schema = xsd.getSchema();
mappings.putAll(schema.getQNamePrefixToNamespaceMap());
}
mappings.putAll(configuration.getXSD().getSchema().getQNamePrefixToNamespaceMap());
} catch (IOException e) {
throw new RuntimeException(e);
}
for (Iterator m = mappings.entrySet().iterator(); m.hasNext();) {
Map.Entry mapping = (Map.Entry) m.next();
String key = (String) mapping.getKey();
if (key == null) {
key = "";
}
namespaces.declarePrefix(key, (String) mapping.getValue());
}
context.registerComponentInstance(namespaces);
context.registerComponentInstance(new NamespaceSupportWrapper(namespaces));
SchemaIndex index = new SchemaIndexImpl( new XSDSchema[]{configuration.schema()} );
context.registerComponentInstance(index);
context.registerComponentInstance(configuration);
return bindingLoader.loadBinding(name, context);
}
/**
* Convenience method which parses the specified string into a dom and sets
* the built document which is to be parsed.
*
* @param xml A string of xml
*/
protected void buildDocument(String xml) throws Exception {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
document = docFactory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes()));
}
/**
* Convenience method for finding a node in a document which matches the
* specified name.
*
*/
protected Element getElementByQName(Document dom, QName name) {
return getElementByQName(dom.getDocumentElement(), name);
}
/**
* Convenience method for finding a single descendant of a particular node
* which matches the specified name.
*
*/
protected Element getElementByQName(Element parent, QName name) {
NodeList nodes = parent.getElementsByTagNameNS(name.getNamespaceURI(), name.getLocalPart());
if (nodes.getLength() == 0) {
return null;
}
return (Element) nodes.item(0);
}
/**
* Convenience method for finding nodes in a document which matche the
* specified name.
*
*/
protected NodeList getElementsByQName(Document dom, QName name) {
return getElementsByQName(dom.getDocumentElement(), name);
}
/**
* Convenience method for finding decendants of a particular node which match
* the specified name.
*
*/
protected NodeList getElementsByQName(Element parent, QName name) {
return parent.getElementsByTagNameNS(name.getNamespaceURI(), name.getLocalPart());
}
}