View Javadoc

1   /*
2    * Copyright 2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.server.endpoint;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.Reader;
25  import java.util.Locale;
26  import javax.xml.stream.XMLEventReader;
27  import javax.xml.stream.XMLStreamConstants;
28  import javax.xml.stream.XMLStreamException;
29  import javax.xml.stream.XMLStreamReader;
30  import javax.xml.transform.Source;
31  import javax.xml.transform.stream.StreamSource;
32  
33  import nu.xom.Attribute;
34  import nu.xom.Builder;
35  import nu.xom.Document;
36  import nu.xom.Element;
37  import nu.xom.NodeFactory;
38  import nu.xom.ParentNode;
39  import nu.xom.ParsingException;
40  import nu.xom.Serializer;
41  import nu.xom.ValidityException;
42  import nu.xom.converters.DOMConverter;
43  import org.w3c.dom.Node;
44  import org.xml.sax.InputSource;
45  import org.xml.sax.SAXException;
46  import org.xml.sax.XMLReader;
47  
48  import org.springframework.core.NestedRuntimeException;
49  import org.springframework.xml.namespace.QNameUtils;
50  import org.springframework.xml.transform.TransformerObjectSupport;
51  import org.springframework.xml.transform.TraxUtils;
52  
53  /**
54   * Abstract base class for endpoints that handle the message payload as XOM elements. Offers the message payload as a
55   * XOM <code>Element</code>, and allows subclasses to create a response by returning an <code>Element</code>.
56   * <p/>
57   * An <code>AbstractXomPayloadEndpoint</code> only accept one payload element. Multiple payload elements are not in
58   * accordance with WS-I.
59   *
60   * @author Arjen Poutsma
61   * @see Element
62   * @since 1.0.0
63   */
64  public abstract class AbstractXomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint {
65  
66      public final Source invoke(Source request) throws Exception {
67          Element requestElement = null;
68          if (request != null) {
69              XomSourceCallback sourceCallback = new XomSourceCallback();
70              try {
71                  TraxUtils.doWithSource(request, sourceCallback);
72              }
73              catch (XomParsingException ex) {
74                  throw (ParsingException) ex.getCause();
75              }
76              requestElement = sourceCallback.element;
77          }
78          Element responseElement = invokeInternal(requestElement);
79          return responseElement != null ? convertResponse(responseElement) : null;
80      }
81  
82      private Source convertResponse(Element responseElement) throws IOException {
83          ByteArrayOutputStream os = new ByteArrayOutputStream();
84          Serializer serializer = createSerializer(os);
85          Document document = responseElement.getDocument();
86          if (document == null) {
87              document = new Document(responseElement);
88          }
89          serializer.write(document);
90          byte[] bytes = os.toByteArray();
91          return new StreamSource(new ByteArrayInputStream(bytes));
92      }
93  
94      /**
95       * Creates a {@link Serializer} to be used for writing the response to.
96       * <p/>
97       * Default implementation uses the UTF-8 encoding and does not set any options, but this may be changed in
98       * subclasses.
99       *
100      * @param outputStream the output stream to serialize to
101      * @return the serializer
102      */
103     protected Serializer createSerializer(OutputStream outputStream) {
104         return new Serializer(outputStream);
105     }
106 
107     /**
108      * Template method. Subclasses must implement this. Offers the request payload as a XOM <code>Element</code>, and
109      * allows subclasses to return a response <code>Element</code>.
110      *
111      * @param requestElement the contents of the SOAP message as XOM element
112      * @return the response element. Can be <code>null</code> to specify no response.
113      */
114     protected abstract Element invokeInternal(Element requestElement) throws Exception;
115 
116     private static class XomSourceCallback implements TraxUtils.SourceCallback {
117 
118         private Element element;
119 
120         public void domSource(Node node) {
121             if (node.getNodeType() == Node.ELEMENT_NODE) {
122                 element = DOMConverter.convert((org.w3c.dom.Element) node);
123             }
124             else if (node.getNodeType() == Node.DOCUMENT_NODE) {
125                 Document document = DOMConverter.convert((org.w3c.dom.Document) node);
126                 element = document.getRootElement();
127             }
128             else {
129                 throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
130             }
131         }
132 
133         public void saxSource(XMLReader reader, InputSource inputSource) throws IOException, SAXException {
134             try {
135                 Builder builder = new Builder(reader);
136                 Document document;
137                 if (inputSource.getByteStream() != null) {
138                     document = builder.build(inputSource.getByteStream());
139                 }
140                 else if (inputSource.getCharacterStream() != null) {
141                     document = builder.build(inputSource.getCharacterStream());
142                 }
143                 else {
144                     throw new IllegalArgumentException(
145                             "InputSource in SAXSource contains neither byte stream nor character stream");
146                 }
147                 element = document.getRootElement();
148             }
149             catch (ValidityException ex) {
150                 throw new XomParsingException(ex);
151             }
152             catch (ParsingException ex) {
153                 throw new XomParsingException(ex);
154             }
155         }
156 
157         public void staxSource(XMLEventReader eventReader) throws XMLStreamException {
158             throw new IllegalArgumentException("XMLEventReader not supported");
159         }
160 
161         public void staxSource(XMLStreamReader streamReader) throws XMLStreamException {
162             Document document = StaxStreamConverter.convert(streamReader);
163             element = document.getRootElement();
164         }
165 
166         public void streamSource(InputStream inputStream) throws IOException {
167             try {
168                 Builder builder = new Builder();
169                 Document document = builder.build(inputStream);
170                 element = document.getRootElement();
171             }
172             catch (ParsingException ex) {
173                 throw new XomParsingException(ex);
174             }
175         }
176 
177         public void streamSource(Reader reader) throws IOException {
178             try {
179                 Builder builder = new Builder();
180                 Document document = builder.build(reader);
181                 element = document.getRootElement();
182             }
183             catch (ParsingException ex) {
184                 throw new XomParsingException(ex);
185             }
186         }
187     }
188 
189     private static class XomParsingException extends NestedRuntimeException {
190 
191         private XomParsingException(ParsingException ex) {
192             super(ex.getMessage(), ex);
193         }
194     }
195 
196     private static class StaxStreamConverter {
197 
198         private static Document convert(XMLStreamReader streamReader) throws XMLStreamException {
199             NodeFactory nodeFactory = new NodeFactory();
200             Document document = null;
201             Element element = null;
202             ParentNode parent = null;
203             boolean documentFinished = false;
204             while (streamReader.hasNext()) {
205                 int event = streamReader.next();
206                 switch (event) {
207                     case XMLStreamConstants.START_DOCUMENT:
208                         document = nodeFactory.startMakingDocument();
209                         parent = document;
210                         break;
211                     case XMLStreamConstants.END_DOCUMENT:
212                         nodeFactory.finishMakingDocument(document);
213                         documentFinished = true;
214                         break;
215                     case XMLStreamConstants.START_ELEMENT:
216                         if (document == null) {
217                             document = nodeFactory.startMakingDocument();
218                             parent = document;
219                         }
220                         String name = QNameUtils.toQualifiedName(streamReader.getName());
221                         if (element == null) {
222                             element = nodeFactory.makeRootElement(name, streamReader.getNamespaceURI());
223                             document.setRootElement(element);
224                         }
225                         else {
226                             element = nodeFactory.startMakingElement(name, streamReader.getNamespaceURI());
227                             parent.appendChild(element);
228                         }
229                         convertNamespaces(streamReader, element);
230                         convertAttributes(streamReader, nodeFactory);
231                         parent = element;
232                         break;
233                     case XMLStreamConstants.END_ELEMENT:
234                         nodeFactory.finishMakingElement(element);
235                         parent = parent.getParent();
236                         break;
237                     case XMLStreamConstants.ATTRIBUTE:
238                         convertAttributes(streamReader, nodeFactory);
239                         break;
240                     case XMLStreamConstants.CHARACTERS:
241                         nodeFactory.makeText(streamReader.getText());
242                         break;
243                     case XMLStreamConstants.COMMENT:
244                         nodeFactory.makeComment(streamReader.getText());
245                         break;
246                     default:
247                         break;
248                 }
249             }
250             if (!documentFinished) {
251                 nodeFactory.finishMakingDocument(document);
252             }
253             return document;
254         }
255 
256         private static void convertNamespaces(XMLStreamReader streamReader, Element element) {
257             for (int i = 0; i < streamReader.getNamespaceCount(); i++) {
258                 String uri = streamReader.getNamespaceURI(i);
259                 String prefix = streamReader.getNamespacePrefix(i);
260 
261                 element.addNamespaceDeclaration(prefix, uri);
262             }
263 
264         }
265 
266         private static void convertAttributes(XMLStreamReader streamReader, NodeFactory nodeFactory) {
267             for (int i = 0; i < streamReader.getAttributeCount(); i++) {
268                 String name = QNameUtils.toQualifiedName(streamReader.getAttributeName(i));
269                 String uri = streamReader.getAttributeNamespace(i);
270                 String value = streamReader.getAttributeValue(i);
271                 Attribute.Type type = convertAttributeType(streamReader.getAttributeType(i));
272 
273                 nodeFactory.makeAttribute(name, uri, value, type);
274             }
275         }
276 
277         private static Attribute.Type convertAttributeType(String type) {
278             type = type.toUpperCase(Locale.ENGLISH);
279             if ("CDATA".equals(type)) {
280                 return Attribute.Type.CDATA;
281             }
282             else if ("ENTITIES".equals(type)) {
283                 return Attribute.Type.ENTITIES;
284             }
285             else if ("ENTITY".equals(type)) {
286                 return Attribute.Type.ENTITY;
287             }
288             else if ("ENUMERATION".equals(type)) {
289                 return Attribute.Type.ENUMERATION;
290             }
291             else if ("ID".equals(type)) {
292                 return Attribute.Type.ID;
293             }
294             else if ("IDREF".equals(type)) {
295                 return Attribute.Type.IDREF;
296             }
297             else if ("IDREFS".equals(type)) {
298                 return Attribute.Type.IDREFS;
299             }
300             else if ("NMTOKEN".equals(type)) {
301                 return Attribute.Type.NMTOKEN;
302             }
303             else if ("NMTOKENS".equals(type)) {
304                 return Attribute.Type.NMTOKENS;
305             }
306             else if ("NOTATION".equals(type)) {
307                 return Attribute.Type.NOTATION;
308             }
309             else {
310                 return Attribute.Type.UNDECLARED;
311             }
312         }
313 
314     }
315 }