This commit is contained in:
Arjen Poutsma
2008-07-31 11:12:56 +00:00
parent 208a7bb756
commit a9de6888f9
5 changed files with 398 additions and 107 deletions

View File

@@ -17,12 +17,17 @@
package org.springframework.ws.server.endpoint;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.DOMReader;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;
import org.w3c.dom.Node;
import org.springframework.xml.transform.TransformerObjectSupport;
/**
@@ -38,6 +43,17 @@ import org.springframework.xml.transform.TransformerObjectSupport;
*/
public abstract class AbstractDom4jPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint {
private boolean alwaysTransform = false;
/**
* Set if the request {@link Source} should always be transformed into a new {@link DocumentResult}.
* <p/>
* Default is {@code false}, which is faster.
*/
public void setAlwaysTransform(boolean alwaysTransform) {
this.alwaysTransform = alwaysTransform;
}
public final Source invoke(Source request) throws Exception {
Element requestElement = null;
if (request != null) {
@@ -50,6 +66,38 @@ public abstract class AbstractDom4jPayloadEndpoint extends TransformerObjectSupp
return responseElement != null ? new DocumentSource(responseElement) : null;
}
/**
* Returns the payload element of the given source.
* <p/>
* Default implementation checks whether the source is a {@link javax.xml.transform.dom.DOMSource}, and uses a
* {@link org.jdom.input.DOMBuilder} to create a JDOM {@link org.jdom.Element}. In all other cases, or when
* {@linkplain #setAlwaysTransform(boolean) alwaysTransform} is {@code true}, the source is transformed into a
* {@link org.jdom.transform.JDOMResult}, which is more expensive. If the passed source is {@code null}, {@code
* null} is returned.
*
* @param source the source to return the root element of; can be {@code null}
* @return the document element
* @throws javax.xml.transform.TransformerException
* in case of errors
*/
protected Element getDocumentElement(Source source) throws TransformerException {
if (source == null) {
return null;
}
if (!alwaysTransform && source instanceof DOMSource) {
Node node = ((DOMSource) source).getNode();
if (node.getNodeType() == Node.DOCUMENT_NODE) {
DOMReader domReader = new DOMReader();
Document document = domReader.read((org.w3c.dom.Document) node);
return document.getRootElement();
}
}
// we have no other option than to transform
DocumentResult dom4jResult = new DocumentResult();
transform(source, dom4jResult);
return dom4jResult.getDocument().getRootElement();
}
/**
* Template method. Subclasses must implement this. Offers the request payload as a dom4j <code>Element</code>, and
* allows subclasses to return a response <code>Element</code>.

View File

@@ -20,20 +20,23 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.springframework.xml.transform.TransformerObjectSupport;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.springframework.xml.transform.TransformerObjectSupport;
/**
* Abstract base class for endpoints that handle the message payload as DOM elements.
* <p/>
* <p>Offers the message payload as a DOM <code>Element</code>, and allows subclasses to create a response by returning
* an <code>Element</code>.
* Offers the message payload as a DOM <code>Element</code>, and allows subclasses to create a response by returning an
* <code>Element</code>.
* <p/>
* <p>An <code>AbstractDomPayloadEndpoint</code> only accept <i>one</i> payload element. Multiple payload elements are
* An <code>AbstractDomPayloadEndpoint</code> only accept <em>one</em> payload element. Multiple payload elements are
* not in accordance with WS-I.
*
* @author Arjen Poutsma
@@ -49,6 +52,8 @@ public abstract class AbstractDomPayloadEndpoint extends TransformerObjectSuppor
private boolean namespaceAware = true;
private boolean alwaysTransform = false;
/** Set whether or not the XML parser should be XML namespace aware. Default is <code>true</code>. */
public void setNamespaceAware(boolean namespaceAware) {
this.namespaceAware = namespaceAware;
@@ -59,26 +64,24 @@ public abstract class AbstractDomPayloadEndpoint extends TransformerObjectSuppor
this.validating = validating;
}
/**
* Set if the request {@link Source} should always be transformed into a new {@link DOMResult}.
* <p/>
* Default is {@code false}, which is faster.
*/
public void setAlwaysTransform(boolean alwaysTransform) {
this.alwaysTransform = alwaysTransform;
}
public final Source invoke(Source request) throws Exception {
if (documentBuilderFactory == null) {
documentBuilderFactory = createDocumentBuilderFactory();
}
DocumentBuilder documentBuilder = createDocumentBuilder(documentBuilderFactory);
Element requestElement = null;
if (request != null) {
Document requestDocument = documentBuilder.newDocument();
DOMResult domResult = new DOMResult(requestDocument);
transform(request, domResult);
requestElement = (Element) requestDocument.getFirstChild();
}
Element requestElement = getDocumentElement(request, documentBuilder);
Document responseDocument = documentBuilder.newDocument();
Element responseElement = invokeInternal(requestElement, responseDocument);
if (responseElement != null) {
return new DOMSource(responseElement);
}
else {
return null;
}
return responseElement != null ? new DOMSource(responseElement) : null;
}
/**
@@ -109,6 +112,39 @@ public abstract class AbstractDomPayloadEndpoint extends TransformerObjectSuppor
return factory;
}
/**
* Returns the payload element of the given source.
* <p/>
* Default implementation checks whether the source is a {@link DOMSource}, and returns the {@linkplain
* DOMSource#getNode() node} of that. In all other cases, or when {@linkplain #setAlwaysTransform(boolean)
* alwaysTransform} is {@code true}, the source is transformed into a {@link DOMResult}, which is more expensive. If
* the passed source is {@code null}, {@code null} is returned.
*
* @param source the source to return the root element of; can be {@code null}
* @param documentBuilder the document builder to be used for transformations
* @return the document element
* @throws TransformerException in case of errors
*/
protected Element getDocumentElement(Source source, DocumentBuilder documentBuilder) throws TransformerException {
if (source == null) {
return null;
}
if (!alwaysTransform && source instanceof DOMSource) {
Node node = ((DOMSource) source).getNode();
if (node.getNodeType() == Node.ELEMENT_NODE) {
return (Element) node;
}
else if (node.getNodeType() == Node.DOCUMENT_NODE) {
return ((Document) node).getDocumentElement();
}
}
// we have no other option than to transform
Document requestDocument = documentBuilder.newDocument();
DOMResult domResult = new DOMResult(requestDocument);
transform(source, domResult);
return requestDocument.getDocumentElement();
}
/**
* Template method that subclasses must implement to process the request.
* <p/>
@@ -123,4 +159,5 @@ public abstract class AbstractDomPayloadEndpoint extends TransformerObjectSuppor
* @return the response element. Can be <code>null</code> to specify no response.
*/
protected abstract Element invokeInternal(Element requestElement, Document responseDocument) throws Exception;
}

View File

@@ -17,10 +17,16 @@
package org.springframework.ws.server.endpoint;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.DOMBuilder;
import org.jdom.transform.JDOMResult;
import org.jdom.transform.JDOMSource;
import org.w3c.dom.Node;
import org.springframework.xml.transform.TransformerObjectSupport;
/**
@@ -37,17 +43,56 @@ import org.springframework.xml.transform.TransformerObjectSupport;
*/
public abstract class AbstractJDomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint {
private boolean alwaysTransform = false;
/**
* Set if the request {@link Source} should always be transformed into a new {@link JDOMResult}.
* <p/>
* Default is {@code false}, which is faster.
*/
public void setAlwaysTransform(boolean alwaysTransform) {
this.alwaysTransform = alwaysTransform;
}
public final Source invoke(Source request) throws Exception {
Element requestElement = null;
if (request != null) {
JDOMResult jdomResult = new JDOMResult();
transform(request, jdomResult);
requestElement = jdomResult.getDocument().getRootElement();
}
Element requestElement = getDocumentElement(request);
Element responseElement = invokeInternal(requestElement);
return responseElement != null ? new JDOMSource(responseElement) : null;
}
/**
* Returns the payload element of the given source.
* <p/>
* Default implementation checks whether the source is a {@link DOMSource}, and uses a {@link DOMBuilder} to create
* a JDOM {@link Element}. In all other cases, or when {@linkplain #setAlwaysTransform(boolean) alwaysTransform} is
* {@code true}, the source is transformed into a {@link JDOMResult}, which is more expensive. If the passed source
* is {@code null}, {@code null} is returned.
*
* @param source the source to return the root element of; can be {@code null}
* @return the document element
* @throws TransformerException in case of errors
*/
protected Element getDocumentElement(Source source) throws TransformerException {
if (source == null) {
return null;
}
if (!alwaysTransform && source instanceof DOMSource) {
Node node = ((DOMSource) source).getNode();
DOMBuilder domBuilder = new DOMBuilder();
if (node.getNodeType() == Node.ELEMENT_NODE) {
return domBuilder.build((org.w3c.dom.Element) node);
}
else if (node.getNodeType() == Node.DOCUMENT_NODE) {
Document document = domBuilder.build((org.w3c.dom.Document) node);
return document.getRootElement();
}
}
// we have no other option than to transform
JDOMResult jdomResult = new JDOMResult();
transform(source, jdomResult);
return jdomResult.getDocument().getRootElement();
}
/**
* Template method. Subclasses must implement this. Offers the request payload as a JDOM <code>Element</code>, and
* allows subclasses to return a response <code>Element</code>.

View File

@@ -16,23 +16,39 @@
package org.springframework.ws.server.endpoint;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Locale;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import nu.xom.Attribute;
import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.NodeFactory;
import nu.xom.ParentNode;
import nu.xom.ParsingException;
import nu.xom.Serializer;
import nu.xom.ValidityException;
import nu.xom.converters.DOMConverter;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.springframework.core.NestedRuntimeException;
import org.springframework.xml.namespace.QNameUtils;
import org.springframework.xml.transform.TransformerObjectSupport;
import org.springframework.xml.transform.TraxUtils;
/**
* Abstract base class for endpoints that handle the message payload as XOM elements. Offers the message payload as a
@@ -47,91 +63,45 @@ import org.springframework.xml.transform.TransformerObjectSupport;
*/
public abstract class AbstractXomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint {
private DocumentBuilderFactory documentBuilderFactory;
public final Source invoke(Source request) throws Exception {
Element requestElement = null;
if (request != null) {
if (request instanceof DOMSource) {
requestElement = handleDomSource((DOMSource) request);
XomSourceCallback sourceCallback = new XomSourceCallback();
try {
TraxUtils.doWithSource(request, sourceCallback);
}
else if (request instanceof SAXSource) {
requestElement = handleSaxSource((SAXSource) request);
}
else if (request instanceof StreamSource) {
requestElement = handleStreamSource((StreamSource) request);
}
else {
throw new IllegalArgumentException("Source [" + request.getClass().getName() +
"] is neither SAXSource, DOMSource, nor StreamSource");
catch (XomParsingException ex) {
throw (ParsingException) ex.getCause();
}
requestElement = sourceCallback.element;
}
Element responseElement = invokeInternal(requestElement);
Source result;
if (responseElement != null) {
if (documentBuilderFactory == null) {
createDocumentBuilderFactory();
}
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document responseDocument = new Document(responseElement);
org.w3c.dom.Document w3cDocument =
DOMConverter.convert(responseDocument, documentBuilder.getDOMImplementation());
return new DOMSource(w3cDocument);
}
else {
result = null;
}
return result;
return responseElement != null ? convertResponse(responseElement) : null;
}
private Element handleDomSource(DOMSource request) {
Node w3cNode = request.getNode();
org.w3c.dom.Element w3cElement = null;
if (w3cNode.getNodeType() == Node.ELEMENT_NODE) {
w3cElement = (org.w3c.dom.Element) w3cNode;
private Source convertResponse(Element responseElement) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Serializer serializer = createSerializer(os);
Document document = responseElement.getDocument();
if (document == null) {
document = new Document(responseElement);
}
else if (w3cNode.getNodeType() == Node.DOCUMENT_NODE) {
org.w3c.dom.Document w3cDocument = (org.w3c.dom.Document) w3cNode;
w3cElement = w3cDocument.getDocumentElement();
}
return DOMConverter.convert(w3cElement);
serializer.write(document);
byte[] bytes = os.toByteArray();
return new StreamSource(new ByteArrayInputStream(bytes));
}
private Element handleSaxSource(SAXSource request) throws ParsingException, IOException {
Builder builder = new Builder(request.getXMLReader());
InputSource inputSource = request.getInputSource();
Document document;
if (inputSource.getByteStream() != null) {
document = builder.build(inputSource.getByteStream());
}
else if (inputSource.getCharacterStream() != null) {
document = builder.build(inputSource.getCharacterStream());
}
else {
throw new IllegalArgumentException(
"InputSource in SAXSource contains neither byte stream nor " + "character stream");
}
return document.getRootElement();
}
private Element handleStreamSource(StreamSource request) throws ParsingException, IOException {
Builder builder = new Builder();
Document document;
if (request.getInputStream() != null) {
document = builder.build(request.getInputStream());
}
else if (request.getReader() != null) {
document = builder.build(request.getReader());
}
else {
throw new IllegalArgumentException("StreamSource contains neither byte stream nor character stream");
}
return document.getRootElement();
}
private void createDocumentBuilderFactory() {
documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
/**
* Creates a {@link Serializer} to be used for writing the response to.
* <p/>
* Default implementation uses the UTF-8 encoding and does not set any options, but this may be changed in
* subclasses.
*
* @param outputStream the output stream to serialize to
* @return the serializer
*/
protected Serializer createSerializer(OutputStream outputStream) {
return new Serializer(outputStream);
}
/**
@@ -143,5 +113,203 @@ public abstract class AbstractXomPayloadEndpoint extends TransformerObjectSuppor
*/
protected abstract Element invokeInternal(Element requestElement) throws Exception;
private static class XomSourceCallback implements TraxUtils.SourceCallback {
private Element element;
public void domSource(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
element = DOMConverter.convert((org.w3c.dom.Element) node);
}
else if (node.getNodeType() == Node.DOCUMENT_NODE) {
Document document = DOMConverter.convert((org.w3c.dom.Document) node);
element = document.getRootElement();
}
else {
throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
}
}
public void saxSource(XMLReader reader, InputSource inputSource) throws IOException, SAXException {
try {
Builder builder = new Builder(reader);
Document document;
if (inputSource.getByteStream() != null) {
document = builder.build(inputSource.getByteStream());
}
else if (inputSource.getCharacterStream() != null) {
document = builder.build(inputSource.getCharacterStream());
}
else {
throw new IllegalArgumentException(
"InputSource in SAXSource contains neither byte stream nor character stream");
}
element = document.getRootElement();
}
catch (ValidityException ex) {
throw new XomParsingException(ex);
}
catch (ParsingException ex) {
throw new XomParsingException(ex);
}
}
public void staxSource(XMLEventReader eventReader) throws XMLStreamException {
throw new IllegalArgumentException("XMLEventReader not supported");
}
public void staxSource(XMLStreamReader streamReader) throws XMLStreamException {
Document document = StaxStreamConverter.convert(streamReader);
element = document.getRootElement();
}
public void streamSource(InputStream inputStream) throws IOException {
try {
Builder builder = new Builder();
Document document = builder.build(inputStream);
element = document.getRootElement();
}
catch (ParsingException ex) {
throw new XomParsingException(ex);
}
}
public void streamSource(Reader reader) throws IOException {
try {
Builder builder = new Builder();
Document document = builder.build(reader);
element = document.getRootElement();
}
catch (ParsingException ex) {
throw new XomParsingException(ex);
}
}
}
private static class XomParsingException extends NestedRuntimeException {
private XomParsingException(ParsingException ex) {
super(ex.getMessage(), ex);
}
}
private static class StaxStreamConverter {
private static Document convert(XMLStreamReader streamReader) throws XMLStreamException {
NodeFactory nodeFactory = new NodeFactory();
Document document = null;
Element element = null;
ParentNode parent = null;
boolean documentFinished = false;
while (streamReader.hasNext()) {
int event = streamReader.next();
switch (event) {
case XMLStreamConstants.START_DOCUMENT:
document = nodeFactory.startMakingDocument();
parent = document;
break;
case XMLStreamConstants.END_DOCUMENT:
nodeFactory.finishMakingDocument(document);
documentFinished = true;
break;
case XMLStreamConstants.START_ELEMENT:
if (document == null) {
document = nodeFactory.startMakingDocument();
parent = document;
}
String name = QNameUtils.toQualifiedName(streamReader.getName());
if (element == null) {
element = nodeFactory.makeRootElement(name, streamReader.getNamespaceURI());
document.setRootElement(element);
}
else {
element = nodeFactory.startMakingElement(name, streamReader.getNamespaceURI());
parent.appendChild(element);
}
convertNamespaces(streamReader, element);
convertAttributes(streamReader, nodeFactory);
parent = element;
break;
case XMLStreamConstants.END_ELEMENT:
nodeFactory.finishMakingElement(element);
parent = parent.getParent();
break;
case XMLStreamConstants.ATTRIBUTE:
convertAttributes(streamReader, nodeFactory);
break;
case XMLStreamConstants.CHARACTERS:
nodeFactory.makeText(streamReader.getText());
break;
case XMLStreamConstants.COMMENT:
nodeFactory.makeComment(streamReader.getText());
break;
default:
break;
}
}
if (!documentFinished) {
nodeFactory.finishMakingDocument(document);
}
return document;
}
private static void convertNamespaces(XMLStreamReader streamReader, Element element) {
for (int i = 0; i < streamReader.getNamespaceCount(); i++) {
String uri = streamReader.getNamespaceURI(i);
String prefix = streamReader.getNamespacePrefix(i);
element.addNamespaceDeclaration(prefix, uri);
}
}
private static void convertAttributes(XMLStreamReader streamReader, NodeFactory nodeFactory) {
for (int i = 0; i < streamReader.getAttributeCount(); i++) {
String name = QNameUtils.toQualifiedName(streamReader.getAttributeName(i));
String uri = streamReader.getAttributeNamespace(i);
String value = streamReader.getAttributeValue(i);
Attribute.Type type = convertAttributeType(streamReader.getAttributeType(i));
nodeFactory.makeAttribute(name, uri, value, type);
}
}
private static Attribute.Type convertAttributeType(String type) {
type = type.toUpperCase(Locale.ENGLISH);
if ("CDATA".equals(type)) {
return Attribute.Type.CDATA;
}
else if ("ENTITIES".equals(type)) {
return Attribute.Type.ENTITIES;
}
else if ("ENTITY".equals(type)) {
return Attribute.Type.ENTITY;
}
else if ("ENUMERATION".equals(type)) {
return Attribute.Type.ENUMERATION;
}
else if ("ID".equals(type)) {
return Attribute.Type.ID;
}
else if ("IDREF".equals(type)) {
return Attribute.Type.IDREF;
}
else if ("IDREFS".equals(type)) {
return Attribute.Type.IDREFS;
}
else if ("NMTOKEN".equals(type)) {
return Attribute.Type.NMTOKEN;
}
else if ("NMTOKENS".equals(type)) {
return Attribute.Type.NMTOKENS;
}
else if ("NOTATION".equals(type)) {
return Attribute.Type.NOTATION;
}
else {
return Attribute.Type.UNDECLARED;
}
}
}
}

View File

@@ -59,11 +59,4 @@ public class XomPayloadEndpointTest extends AbstractPayloadEndpointTestCase {
// overriden, because XOM doesn not support it
}
public void testStaxSourceStreamReader() throws Exception {
// overriden, because XOM doesn not support it
}
public void testStaxSourceStreamReaderJaxp14() throws Exception {
// overriden, because XOM doesn not support it
}
}