/* * Copyright 2005-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.ws.server.endpoint; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; 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.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 * XOM {@code Element}, and allows subclasses to create a response by returning an {@code Element}. * * <p>An {@code AbstractXomPayloadEndpoint} only accept one payload element. Multiple payload elements are not in * accordance with WS-I. * * @author Arjen Poutsma * @see Element * @since 1.0.0 * @deprecated as of Spring Web Services 2.0, in favor of annotated endpoints */ @Deprecated @SuppressWarnings("Since15") public abstract class AbstractXomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint { @Override public final Source invoke(Source request) throws Exception { Element requestElement = null; if (request != null) { XomSourceCallback sourceCallback = new XomSourceCallback(); try { TraxUtils.doWithSource(request, sourceCallback); } catch (XomParsingException ex) { throw (ParsingException) ex.getCause(); } requestElement = sourceCallback.element; } Element responseElement = invokeInternal(requestElement); return responseElement != null ? convertResponse(responseElement) : null; } 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); } serializer.write(document); byte[] bytes = os.toByteArray(); return new StreamSource(new ByteArrayInputStream(bytes)); } /** * 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); } /** * Template method. Subclasses must implement this. Offers the request payload as a XOM {@code Element}, and * allows subclasses to return a response {@code Element}. * * @param requestElement the contents of the SOAP message as XOM element * @return the response element. Can be {@code null} to specify no response. */ protected abstract Element invokeInternal(Element requestElement) throws Exception; private static class XomSourceCallback implements TraxUtils.SourceCallback { private Element element; @Override 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"); } } @Override 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); } } @Override public void staxSource(XMLEventReader eventReader) throws XMLStreamException { throw new IllegalArgumentException("XMLEventReader not supported"); } @Override public void staxSource(XMLStreamReader streamReader) throws XMLStreamException { Document document = StaxStreamConverter.convert(streamReader); element = document.getRootElement(); } @Override 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); } } @Override 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); } } @Override public void source(String systemId) throws Exception { try { Builder builder = new Builder(); Document document = builder.build(systemId); element = document.getRootElement(); } catch (ParsingException ex) { throw new XomParsingException(ex); } } } @SuppressWarnings("serial") 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; } } } }