View Javadoc

1   /*
2    * Copyright 2005-2010 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 org.springframework.core.NestedRuntimeException;
34  import org.springframework.xml.namespace.QNameUtils;
35  import org.springframework.xml.transform.TransformerObjectSupport;
36  import org.springframework.xml.transform.TraxUtils;
37  
38  import nu.xom.Attribute;
39  import nu.xom.Builder;
40  import nu.xom.Document;
41  import nu.xom.Element;
42  import nu.xom.NodeFactory;
43  import nu.xom.ParentNode;
44  import nu.xom.ParsingException;
45  import nu.xom.Serializer;
46  import nu.xom.ValidityException;
47  import nu.xom.converters.DOMConverter;
48  import org.w3c.dom.Node;
49  import org.xml.sax.InputSource;
50  import org.xml.sax.SAXException;
51  import org.xml.sax.XMLReader;
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   * @deprecated as of Spring Web Services 2.0, in favor of annotated endpoints
64   */
65  @Deprecated
66  @SuppressWarnings("Since15")
67  public abstract class AbstractXomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint {
68  
69      public final Source invoke(Source request) throws Exception {
70          Element requestElement = null;
71          if (request != null) {
72              XomSourceCallback sourceCallback = new XomSourceCallback();
73              try {
74                  TraxUtils.doWithSource(request, sourceCallback);
75              }
76              catch (XomParsingException ex) {
77                  throw (ParsingException) ex.getCause();
78              }
79              requestElement = sourceCallback.element;
80          }
81          Element responseElement = invokeInternal(requestElement);
82          return responseElement != null ? convertResponse(responseElement) : null;
83      }
84  
85      private Source convertResponse(Element responseElement) throws IOException {
86          ByteArrayOutputStream os = new ByteArrayOutputStream();
87          Serializer serializer = createSerializer(os);
88          Document document = responseElement.getDocument();
89          if (document == null) {
90              document = new Document(responseElement);
91          }
92          serializer.write(document);
93          byte[] bytes = os.toByteArray();
94          return new StreamSource(new ByteArrayInputStream(bytes));
95      }
96  
97      /**
98       * Creates a {@link Serializer} to be used for writing the response to.
99       * <p/>
100      * Default implementation uses the UTF-8 encoding and does not set any options, but this may be changed in
101      * subclasses.
102      *
103      * @param outputStream the output stream to serialize to
104      * @return the serializer
105      */
106     protected Serializer createSerializer(OutputStream outputStream) {
107         return new Serializer(outputStream);
108     }
109 
110     /**
111      * Template method. Subclasses must implement this. Offers the request payload as a XOM <code>Element</code>, and
112      * allows subclasses to return a response <code>Element</code>.
113      *
114      * @param requestElement the contents of the SOAP message as XOM element
115      * @return the response element. Can be <code>null</code> to specify no response.
116      */
117     protected abstract Element invokeInternal(Element requestElement) throws Exception;
118 
119     private static class XomSourceCallback implements TraxUtils.SourceCallback {
120 
121         private Element element;
122 
123         public void domSource(Node node) {
124             if (node.getNodeType() == Node.ELEMENT_NODE) {
125                 element = DOMConverter.convert((org.w3c.dom.Element) node);
126             }
127             else if (node.getNodeType() == Node.DOCUMENT_NODE) {
128                 Document document = DOMConverter.convert((org.w3c.dom.Document) node);
129                 element = document.getRootElement();
130             }
131             else {
132                 throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
133             }
134         }
135 
136         public void saxSource(XMLReader reader, InputSource inputSource) throws IOException, SAXException {
137             try {
138                 Builder builder = new Builder(reader);
139                 Document document;
140                 if (inputSource.getByteStream() != null) {
141                     document = builder.build(inputSource.getByteStream());
142                 }
143                 else if (inputSource.getCharacterStream() != null) {
144                     document = builder.build(inputSource.getCharacterStream());
145                 }
146                 else {
147                     throw new IllegalArgumentException(
148                             "InputSource in SAXSource contains neither byte stream nor character stream");
149                 }
150                 element = document.getRootElement();
151             }
152             catch (ValidityException ex) {
153                 throw new XomParsingException(ex);
154             }
155             catch (ParsingException ex) {
156                 throw new XomParsingException(ex);
157             }
158         }
159 
160         public void staxSource(XMLEventReader eventReader) throws XMLStreamException {
161             throw new IllegalArgumentException("XMLEventReader not supported");
162         }
163 
164         public void staxSource(XMLStreamReader streamReader) throws XMLStreamException {
165             Document document = StaxStreamConverter.convert(streamReader);
166             element = document.getRootElement();
167         }
168 
169         public void streamSource(InputStream inputStream) throws IOException {
170             try {
171                 Builder builder = new Builder();
172                 Document document = builder.build(inputStream);
173                 element = document.getRootElement();
174             }
175             catch (ParsingException ex) {
176                 throw new XomParsingException(ex);
177             }
178         }
179 
180         public void streamSource(Reader reader) throws IOException {
181             try {
182                 Builder builder = new Builder();
183                 Document document = builder.build(reader);
184                 element = document.getRootElement();
185             }
186             catch (ParsingException ex) {
187                 throw new XomParsingException(ex);
188             }
189         }
190 
191         public void source(String systemId) throws Exception {
192             try {
193                 Builder builder = new Builder();
194                 Document document = builder.build(systemId);
195                 element = document.getRootElement();
196             }
197             catch (ParsingException ex) {
198                 throw new XomParsingException(ex);
199             }
200         }
201     }
202 
203     private static class XomParsingException extends NestedRuntimeException {
204 
205         private XomParsingException(ParsingException ex) {
206             super(ex.getMessage(), ex);
207         }
208     }
209 
210     private static class StaxStreamConverter {
211 
212         private static Document convert(XMLStreamReader streamReader) throws XMLStreamException {
213             NodeFactory nodeFactory = new NodeFactory();
214             Document document = null;
215             Element element = null;
216             ParentNode parent = null;
217             boolean documentFinished = false;
218             while (streamReader.hasNext()) {
219                 int event = streamReader.next();
220                 switch (event) {
221                     case XMLStreamConstants.START_DOCUMENT:
222                         document = nodeFactory.startMakingDocument();
223                         parent = document;
224                         break;
225                     case XMLStreamConstants.END_DOCUMENT:
226                         nodeFactory.finishMakingDocument(document);
227                         documentFinished = true;
228                         break;
229                     case XMLStreamConstants.START_ELEMENT:
230                         if (document == null) {
231                             document = nodeFactory.startMakingDocument();
232                             parent = document;
233                         }
234                         String name = QNameUtils.toQualifiedName(streamReader.getName());
235                         if (element == null) {
236                             element = nodeFactory.makeRootElement(name, streamReader.getNamespaceURI());
237                             document.setRootElement(element);
238                         }
239                         else {
240                             element = nodeFactory.startMakingElement(name, streamReader.getNamespaceURI());
241                             parent.appendChild(element);
242                         }
243                         convertNamespaces(streamReader, element);
244                         convertAttributes(streamReader, nodeFactory);
245                         parent = element;
246                         break;
247                     case XMLStreamConstants.END_ELEMENT:
248                         nodeFactory.finishMakingElement(element);
249                         parent = parent.getParent();
250                         break;
251                     case XMLStreamConstants.ATTRIBUTE:
252                         convertAttributes(streamReader, nodeFactory);
253                         break;
254                     case XMLStreamConstants.CHARACTERS:
255                         nodeFactory.makeText(streamReader.getText());
256                         break;
257                     case XMLStreamConstants.COMMENT:
258                         nodeFactory.makeComment(streamReader.getText());
259                         break;
260                     default:
261                         break;
262                 }
263             }
264             if (!documentFinished) {
265                 nodeFactory.finishMakingDocument(document);
266             }
267             return document;
268         }
269 
270         private static void convertNamespaces(XMLStreamReader streamReader, Element element) {
271             for (int i = 0; i < streamReader.getNamespaceCount(); i++) {
272                 String uri = streamReader.getNamespaceURI(i);
273                 String prefix = streamReader.getNamespacePrefix(i);
274 
275                 element.addNamespaceDeclaration(prefix, uri);
276             }
277 
278         }
279 
280         private static void convertAttributes(XMLStreamReader streamReader, NodeFactory nodeFactory) {
281             for (int i = 0; i < streamReader.getAttributeCount(); i++) {
282                 String name = QNameUtils.toQualifiedName(streamReader.getAttributeName(i));
283                 String uri = streamReader.getAttributeNamespace(i);
284                 String value = streamReader.getAttributeValue(i);
285                 Attribute.Type type = convertAttributeType(streamReader.getAttributeType(i));
286 
287                 nodeFactory.makeAttribute(name, uri, value, type);
288             }
289         }
290 
291         private static Attribute.Type convertAttributeType(String type) {
292             type = type.toUpperCase(Locale.ENGLISH);
293             if ("CDATA".equals(type)) {
294                 return Attribute.Type.CDATA;
295             }
296             else if ("ENTITIES".equals(type)) {
297                 return Attribute.Type.ENTITIES;
298             }
299             else if ("ENTITY".equals(type)) {
300                 return Attribute.Type.ENTITY;
301             }
302             else if ("ENUMERATION".equals(type)) {
303                 return Attribute.Type.ENUMERATION;
304             }
305             else if ("ID".equals(type)) {
306                 return Attribute.Type.ID;
307             }
308             else if ("IDREF".equals(type)) {
309                 return Attribute.Type.IDREF;
310             }
311             else if ("IDREFS".equals(type)) {
312                 return Attribute.Type.IDREFS;
313             }
314             else if ("NMTOKEN".equals(type)) {
315                 return Attribute.Type.NMTOKEN;
316             }
317             else if ("NMTOKENS".equals(type)) {
318                 return Attribute.Type.NMTOKENS;
319             }
320             else if ("NOTATION".equals(type)) {
321                 return Attribute.Type.NOTATION;
322             }
323             else {
324                 return Attribute.Type.UNDECLARED;
325             }
326         }
327 
328     }
329 }