001: /*
002: * Copyright 2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.ws.soap.axiom;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.Iterator;
022: import javax.xml.stream.XMLInputFactory;
023: import javax.xml.stream.XMLStreamException;
024: import javax.xml.stream.XMLStreamReader;
025:
026: import org.apache.axiom.attachments.Attachments;
027: import org.apache.axiom.om.OMException;
028: import org.apache.axiom.om.impl.MTOMConstants;
029: import org.apache.axiom.soap.SOAP11Constants;
030: import org.apache.axiom.soap.SOAP12Constants;
031: import org.apache.axiom.soap.SOAPFactory;
032: import org.apache.axiom.soap.SOAPMessage;
033: import org.apache.axiom.soap.impl.builder.MTOMStAXSOAPModelBuilder;
034: import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder;
035: import org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory;
036: import org.apache.axiom.soap.impl.llom.soap12.SOAP12Factory;
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039: import org.springframework.beans.factory.InitializingBean;
040: import org.springframework.util.StringUtils;
041: import org.springframework.ws.WebServiceMessage;
042: import org.springframework.ws.soap.SoapMessageFactory;
043: import org.springframework.ws.soap.SoapVersion;
044: import org.springframework.ws.transport.TransportConstants;
045: import org.springframework.ws.transport.TransportInputStream;
046:
047: /**
048: * Axiom-specific implementation of the {@link org.springframework.ws.WebServiceMessageFactory WebServiceMessageFactory}
049: * interface. Creates {@link org.springframework.ws.soap.axiom.AxiomSoapMessage AxiomSoapMessages}.
050: * <p/>
051: * To increase reading performance on the the SOAP request created by this message context factory, you can set the
052: * <code>payloadCaching</code> property to <code>false</code> (default is <code>true</code>). This this will read the
053: * contents of the body directly from the stream. However, <strong>when this setting is enabled, the payload can only be
054: * read once</strong>. This means that any endpoint mappings or interceptors which are based on the message payload
055: * (such as the <code>PayloadRootQNameEndpointMapping</code>, the <code>PayloadValidatingInterceptor</code>, or the
056: * <code>PayloadLoggingInterceptor</code>) cannot be used. Instead, use an endpoint mapping that does not consume the
057: * payload (i.e. the <code>SoapActionEndpointMapping</code>).
058: * <p/>
059: * Mostly derived from <code>org.apache.axis2.transport.http.HTTPTransportUtils</code> and
060: * <code>org.apache.axis2.transport.TransportUtils</code>, which we cannot use since they are not part of the Axiom
061: * distribution.
062: *
063: * @author Arjen Poutsma
064: * @see AxiomSoapMessage
065: * @see #setPayloadCaching(boolean)
066: * @since 1.0.0
067: */
068: public class AxiomSoapMessageFactory implements SoapMessageFactory,
069: InitializingBean {
070:
071: private static final String CHAR_SET_ENCODING = "charset";
072:
073: private static final String DEFAULT_CHAR_SET_ENCODING = "UTF-8";
074:
075: private static final String MULTI_PART_RELATED_CONTENT_TYPE = "multipart/related";
076:
077: private static final Log logger = LogFactory
078: .getLog(AxiomSoapMessageFactory.class);
079:
080: private XMLInputFactory inputFactory;
081:
082: private boolean payloadCaching = true;
083:
084: // use SOAP 1.1 by default
085: private SOAPFactory soapFactory = new SOAP11Factory();
086:
087: /** Default constructor. */
088: public AxiomSoapMessageFactory() {
089: inputFactory = XMLInputFactory.newInstance();
090: }
091:
092: /**
093: * Indicates whether the SOAP Body payload should be cached or not. Default is <code>true</code>. Setting this to
094: * <code>false</code> will increase performance, but also result in the fact that the message payload can only be
095: * read once.
096: */
097: public void setPayloadCaching(boolean payloadCaching) {
098: this .payloadCaching = payloadCaching;
099: }
100:
101: public void setSoapVersion(SoapVersion version) {
102: if (SoapVersion.SOAP_11 == version) {
103: soapFactory = new SOAP11Factory();
104: } else if (SoapVersion.SOAP_12 == version) {
105: soapFactory = new SOAP12Factory();
106: } else {
107: throw new IllegalArgumentException("Invalid version ["
108: + version + "]. "
109: + "Expected the SOAP_11 or SOAP_12 constant");
110: }
111: }
112:
113: public void afterPropertiesSet() throws Exception {
114: if (logger.isInfoEnabled()) {
115: logger.info(payloadCaching ? "Enabled payload caching"
116: : "Disabled payload caching");
117: }
118: }
119:
120: public WebServiceMessage createWebServiceMessage() {
121: return new AxiomSoapMessage(soapFactory);
122: }
123:
124: public WebServiceMessage createWebServiceMessage(
125: InputStream inputStream) throws IOException {
126: if (!(inputStream instanceof TransportInputStream)) {
127: throw new IllegalArgumentException(
128: "AxiomSoapMessageFactory requires a TransportInputStream");
129: }
130: TransportInputStream transportInputStream = (TransportInputStream) inputStream;
131: String contentType = getHeaderValue(transportInputStream,
132: TransportConstants.HEADER_CONTENT_TYPE);
133: if (!StringUtils.hasLength(contentType)) {
134: throw new IllegalArgumentException(
135: "TransportInputStream contains no Content-Type header");
136: }
137: String soapAction = getHeaderValue(transportInputStream,
138: TransportConstants.HEADER_SOAP_ACTION);
139: try {
140: if (isMultiPartRelated(contentType)) {
141: return createMultiPartAxiomSoapMessage(inputStream,
142: contentType, soapAction);
143: } else {
144: return createAxiomSoapMessage(inputStream, contentType,
145: soapAction);
146: }
147: } catch (XMLStreamException ex) {
148: throw new AxiomSoapMessageCreationException(
149: "Could not parse request: " + ex.getMessage(), ex);
150: } catch (OMException ex) {
151: throw new AxiomSoapMessageCreationException(
152: "Could not create message: " + ex.getMessage(), ex);
153: }
154: }
155:
156: private String getHeaderValue(
157: TransportInputStream transportInputStream, String header)
158: throws IOException {
159: String contentType = null;
160: Iterator iterator = transportInputStream.getHeaders(header);
161: if (iterator.hasNext()) {
162: contentType = (String) iterator.next();
163: }
164: return contentType;
165: }
166:
167: private boolean isMultiPartRelated(String contentType) {
168: return contentType.indexOf(MULTI_PART_RELATED_CONTENT_TYPE) != -1;
169: }
170:
171: /** Creates an AxiomSoapMessage without attachments. */
172: private WebServiceMessage createAxiomSoapMessage(
173: InputStream inputStream, String contentType,
174: String soapAction) throws XMLStreamException {
175: XMLStreamReader reader = inputFactory.createXMLStreamReader(
176: inputStream, getCharSetEncoding(contentType));
177: String envelopeNamespace = getSoapEnvelopeNamespace(contentType);
178: StAXSOAPModelBuilder builder = new StAXSOAPModelBuilder(reader,
179: soapFactory, envelopeNamespace);
180: SOAPMessage soapMessage = builder.getSoapMessage();
181: return new AxiomSoapMessage(soapMessage, soapAction,
182: payloadCaching);
183: }
184:
185: /** Creates an AxiomSoapMessage with attachments. */
186: private AxiomSoapMessage createMultiPartAxiomSoapMessage(
187: InputStream inputStream, String contentType,
188: String soapAction) throws XMLStreamException {
189: Attachments attachments = new Attachments(inputStream,
190: contentType);
191: XMLStreamReader reader = inputFactory
192: .createXMLStreamReader(attachments
193: .getSOAPPartInputStream(),
194: getCharSetEncoding(attachments
195: .getSOAPPartContentType()));
196: StAXSOAPModelBuilder builder = null;
197: String envelopeNamespace = getSoapEnvelopeNamespace(contentType);
198: if (MTOMConstants.SWA_TYPE.equals(attachments
199: .getAttachmentSpecType())
200: || MTOMConstants.SWA_TYPE_12.equals(attachments
201: .getAttachmentSpecType())) {
202: builder = new StAXSOAPModelBuilder(reader, soapFactory,
203: envelopeNamespace);
204: } else if (MTOMConstants.MTOM_TYPE.equals(attachments
205: .getAttachmentSpecType())) {
206: builder = new MTOMStAXSOAPModelBuilder(reader, attachments,
207: envelopeNamespace);
208: } else {
209: throw new AxiomSoapMessageCreationException(
210: "Unknown attachment type: ["
211: + attachments.getAttachmentSpecType() + "]");
212: }
213: return new AxiomSoapMessage(builder.getSoapMessage(),
214: attachments, soapAction, payloadCaching);
215: }
216:
217: private String getSoapEnvelopeNamespace(String contentType) {
218: if (contentType.indexOf(SOAP11Constants.SOAP_11_CONTENT_TYPE) != -1) {
219: return SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
220: } else if (contentType
221: .indexOf(SOAP12Constants.SOAP_12_CONTENT_TYPE) != -1) {
222: return SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI;
223: } else {
224: throw new AxiomSoapMessageCreationException(
225: "Unknown content type '" + contentType + "'");
226: }
227:
228: }
229:
230: /**
231: * Returns the character set from the given content type. Mostly copied
232: *
233: * @return the character set encoding
234: */
235: protected String getCharSetEncoding(String contentType) {
236: int index = contentType.indexOf(CHAR_SET_ENCODING);
237: if (index == -1) {
238: return DEFAULT_CHAR_SET_ENCODING;
239: }
240: int idx = contentType.indexOf("=", index);
241:
242: int indexOfSemiColon = contentType.indexOf(";", idx);
243: String value;
244:
245: if (indexOfSemiColon > 0) {
246: value = contentType.substring(idx + 1, indexOfSemiColon);
247: } else {
248: value = contentType
249: .substring(idx + 1, contentType.length()).trim();
250: }
251: if (value.charAt(0) == '"'
252: && value.charAt(value.length() - 1) == '"') {
253: return value.substring(1, value.length() - 1);
254: }
255: if ("null".equalsIgnoreCase(value)) {
256: return DEFAULT_CHAR_SET_ENCODING;
257: } else {
258: return value.trim();
259: }
260: }
261:
262: public String toString() {
263: StringBuffer buffer = new StringBuffer(
264: "AxiomSoapMessageFactory[");
265: if (soapFactory instanceof SOAP11Factory) {
266: buffer.append("SOAP 1.1");
267: } else if (soapFactory instanceof SOAP12Factory) {
268: buffer.append("SOAP 1.2");
269: }
270: buffer.append(']');
271: return buffer.toString();
272: }
273: }
|