001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019:
020: package org.apache.axis2.transport;
021:
022: import org.apache.axiom.attachments.Attachments;
023: import org.apache.axiom.attachments.CachedFileDataSource;
024: import org.apache.axiom.om.OMElement;
025: import org.apache.axiom.om.OMException;
026: import org.apache.axiom.om.OMOutputFormat;
027: import org.apache.axiom.om.impl.builder.StAXBuilder;
028: import org.apache.axiom.soap.SOAP11Constants;
029: import org.apache.axiom.soap.SOAP12Constants;
030: import org.apache.axiom.soap.SOAPEnvelope;
031: import org.apache.axiom.soap.SOAPFactory;
032: import org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory;
033: import org.apache.axis2.AxisFault;
034: import org.apache.axis2.Constants;
035: import org.apache.axis2.builder.Builder;
036: import org.apache.axis2.builder.BuilderUtil;
037: import org.apache.axis2.context.MessageContext;
038: import org.apache.axis2.description.Parameter;
039: import org.apache.axis2.i18n.Messages;
040: import org.apache.axis2.transport.http.ApplicationXMLFormatter;
041: import org.apache.axis2.transport.http.HTTPConstants;
042: import org.apache.axis2.transport.http.SOAPMessageFormatter;
043: import org.apache.axis2.transport.http.XFormURLEncodedFormatter;
044: import org.apache.axis2.util.JavaUtils;
045: import org.apache.axis2.wsdl.WSDLConstants;
046: import org.apache.commons.logging.Log;
047: import org.apache.commons.logging.LogFactory;
048:
049: import javax.activation.DataSource;
050: import javax.xml.parsers.FactoryConfigurationError;
051: import javax.xml.stream.XMLStreamException;
052:
053: import java.io.File;
054: import java.io.InputStream;
055: import java.io.OutputStream;
056:
057: public class TransportUtils {
058:
059: private static final Log log = LogFactory
060: .getLog(TransportUtils.class);
061:
062: public static SOAPEnvelope createSOAPMessage(
063: MessageContext msgContext) throws AxisFault {
064: try {
065: InputStream inStream = (InputStream) msgContext
066: .getProperty(MessageContext.TRANSPORT_IN);
067: msgContext.setProperty(MessageContext.TRANSPORT_IN, null);
068:
069: // this inputstram is set by the TransportSender represents a two
070: // way transport or a Transport Recevier
071: if (inStream == null) {
072: throw new AxisFault(Messages
073: .getMessage("inputstreamNull"));
074: }
075:
076: String contentType = (String) msgContext
077: .getProperty(Constants.Configuration.CONTENT_TYPE);
078:
079: // get the type of char encoding
080: String charSetEnc = (String) msgContext
081: .getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
082: if (charSetEnc == null && contentType != null) {
083: charSetEnc = BuilderUtil
084: .getCharSetEncoding(contentType);
085: } else if (charSetEnc == null) {
086: charSetEnc = MessageContext.DEFAULT_CHAR_SET_ENCODING;
087: }
088: msgContext.setProperty(
089: Constants.Configuration.CHARACTER_SET_ENCODING,
090: charSetEnc);
091:
092: return createSOAPMessage(msgContext, inStream, contentType);
093: } catch (Exception e) {
094: throw AxisFault.makeFault(e);
095: }
096: }
097:
098: /**
099: * Objective of this method is to capture the SOAPEnvelope creation logic
100: * and make it a common for all the transports and to in/out flows.
101: *
102: * @param msgContext
103: * @param inStream
104: * @param contentType
105: * @return the SOAPEnvelope
106: * @throws AxisFault
107: * @throws OMException
108: * @throws XMLStreamException
109: * @throws FactoryConfigurationError
110: */
111: public static SOAPEnvelope createSOAPMessage(
112: MessageContext msgContext, InputStream inStream,
113: String contentType) throws AxisFault, OMException,
114: XMLStreamException, FactoryConfigurationError {
115: OMElement documentElement = createDocumentElement(contentType,
116: msgContext, inStream);
117: return createSOAPEnvelope(documentElement);
118: }
119:
120: public static SOAPEnvelope createSOAPEnvelope(
121: OMElement documentElement) {
122: SOAPEnvelope envelope;
123: // Check whether we have received a SOAPEnvelope or not
124: if (documentElement instanceof SOAPEnvelope) {
125: envelope = (SOAPEnvelope) documentElement;
126: } else {
127: // If it is not a SOAPEnvelope we wrap that with a fake
128: // SOAPEnvelope.
129: SOAPFactory soapFactory = new SOAP11Factory();
130: envelope = soapFactory.getDefaultEnvelope();
131: envelope.getBody().addChild(documentElement);
132: }
133: return envelope;
134: }
135:
136: public static OMElement createDocumentElement(String contentType,
137: MessageContext msgContext, InputStream inStream)
138: throws AxisFault, XMLStreamException {
139: OMElement documentElement = null;
140: String type = null;
141: if (contentType != null) {
142: int index = contentType.indexOf(';');
143: if (index > 0) {
144: type = contentType.substring(0, index);
145: } else {
146: type = contentType;
147: }
148: // Some services send REST responces as text/xml. We should convert it to
149: // application/xml if its a REST response, if not it will try to use the SOAPMessageBuilder.
150: if (HTTPConstants.MEDIA_TYPE_TEXT_XML.equals(type)) {
151: if (msgContext.isServerSide()) {
152: if (msgContext.getSoapAction() == null) {
153: type = HTTPConstants.MEDIA_TYPE_APPLICATION_XML;
154: }
155: } else if (msgContext.isDoingREST()
156: && !msgContext
157: .isPropertyTrue(Constants.Configuration.SOAP_RESPONSE_MEP)) {
158: type = HTTPConstants.MEDIA_TYPE_APPLICATION_XML;
159: }
160: }
161: Builder builder = BuilderUtil.getBuilderFromSelector(type,
162: msgContext);
163: if (log.isDebugEnabled()) {
164: log.debug("createSOAPEnvelope using Builder ("
165: + builder.getClass() + ") selected from type ("
166: + type + ")");
167: }
168: if (builder != null) {
169: documentElement = builder.processDocument(inStream,
170: contentType, msgContext);
171: }
172: }
173: if (documentElement == null) {
174: if (msgContext.isDoingREST()) {
175: if (log.isDebugEnabled()) {
176: log.debug("Could not find a Builder for type ("
177: + type + "). Using REST.");
178: }
179: StAXBuilder builder = BuilderUtil.getPOXBuilder(
180: inStream, null);
181: documentElement = builder.getDocumentElement();
182: } else {
183: // FIXME making soap defualt for the moment..might effect the
184: // performance
185: if (log.isDebugEnabled()) {
186: log.debug("Could not find a Builder for type ("
187: + type + "). Using SOAP.");
188: }
189: String charSetEnc = (String) msgContext
190: .getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
191: StAXBuilder builder = BuilderUtil.getSOAPBuilder(
192: inStream, charSetEnc);
193: documentElement = builder.getDocumentElement();
194: }
195: }
196: return documentElement;
197: }
198:
199: /**
200: * Extracts and returns the character set encoding from the
201: * Content-type header
202: * Example:
203: * Content-Type: text/xml; charset=utf-8
204: *
205: * @param contentType
206: */
207: public static String getCharSetEncoding(String contentType) {
208: if (log.isDebugEnabled()) {
209: log.debug("Input contentType (" + contentType + ")");
210: }
211: int index = contentType
212: .indexOf(HTTPConstants.CHAR_SET_ENCODING);
213:
214: if (index == -1) { // Charset encoding not found in the content-type header
215: // Using the default UTF-8
216: if (log.isDebugEnabled()) {
217: log.debug("CharSetEncoding defaulted ("
218: + MessageContext.DEFAULT_CHAR_SET_ENCODING
219: + ")");
220: }
221: return MessageContext.DEFAULT_CHAR_SET_ENCODING;
222: }
223:
224: // If there are spaces around the '=' sign
225: int indexOfEq = contentType.indexOf("=", index);
226:
227: // There can be situations where "charset" is not the last parameter of the Content-Type header
228: int indexOfSemiColon = contentType.indexOf(";", indexOfEq);
229: String value;
230:
231: if (indexOfSemiColon > 0) {
232: value = (contentType.substring(indexOfEq + 1,
233: indexOfSemiColon));
234: } else {
235: value = (contentType.substring(indexOfEq + 1, contentType
236: .length())).trim();
237: }
238:
239: // There might be "" around the value - if so remove them
240: if (value.indexOf('\"') != -1) {
241: value = value.replaceAll("\"", "");
242: }
243: value = value.trim();
244: if (log.isDebugEnabled()) {
245: log.debug("CharSetEncoding from content-type (" + value
246: + ")");
247: }
248: return value;
249: }
250:
251: public static void writeMessage(MessageContext msgContext,
252: OutputStream out) throws AxisFault {
253: SOAPEnvelope envelope = msgContext.getEnvelope();
254: OMElement outputMessage = envelope;
255:
256: if ((envelope != null) && msgContext.isDoingREST()) {
257: outputMessage = envelope.getBody().getFirstElement();
258: }
259:
260: if (outputMessage != null) {
261: try {
262: OMOutputFormat format = new OMOutputFormat();
263:
264: // Pick the char set encoding from the msgContext
265: String charSetEnc = (String) msgContext
266: .getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
267:
268: format.setDoOptimize(false);
269: format.setDoingSWA(false);
270: format.setCharSetEncoding(charSetEnc);
271: outputMessage.serializeAndConsume(out, format);
272: out.flush();
273: } catch (Exception e) {
274: throw AxisFault.makeFault(e);
275: }
276: } else {
277: throw new AxisFault(Messages.getMessage("outMessageNull"));
278: }
279: }
280:
281: /**
282: * Initial work for a builder selector which selects the builder for a given message format based on the the content type of the recieved message.
283: * content-type to builder mapping can be specified through the Axis2.xml.
284: *
285: * @param msgContext
286: * @return the builder registered against the given content-type
287: * @throws AxisFault
288: */
289: public static MessageFormatter getMessageFormatter(
290: MessageContext msgContext) throws AxisFault {
291: MessageFormatter messageFormatter = null;
292: String messageFormatString = getMessageFormatterProperty(msgContext);
293: if (messageFormatString != null) {
294: messageFormatter = msgContext.getConfigurationContext()
295: .getAxisConfiguration().getMessageFormatter(
296: messageFormatString);
297:
298: }
299: if (messageFormatter == null) {
300:
301: // If we are doing rest better default to Application/xml formatter
302: if (msgContext.isDoingREST()) {
303: String httpMethod = (String) msgContext
304: .getProperty(Constants.Configuration.HTTP_METHOD);
305: if (Constants.Configuration.HTTP_METHOD_GET
306: .equals(httpMethod)
307: || Constants.Configuration.HTTP_METHOD_DELETE
308: .equals(httpMethod)) {
309: return new XFormURLEncodedFormatter();
310: }
311: return new ApplicationXMLFormatter();
312: } else {
313: // Lets default to SOAP formatter
314: //TODO need to improve this to use the stateless nature
315: messageFormatter = new SOAPMessageFormatter();
316: }
317: }
318: return messageFormatter;
319: }
320:
321: /**
322: * @param contentType The contentType of the incoming message. It may be null
323: * @param defaultSOAPNamespace Usually set the version that is expected. This a fallback if the contentType is unavailable or
324: * does not match our expectations
325: * @return null or the soap namespace. A null indicates that the message will be interpretted as a non-SOAP (i.e. REST) message
326: */
327: private static String getSOAPNamespaceFromContentType(
328: String contentType, String defaultSOAPNamespace) {
329:
330: String returnNS = defaultSOAPNamespace;
331: // Discriminate using the content Type
332: if (contentType != null) {
333:
334: /*
335: * SOAP11 content-type is "text/xml"
336: * SOAP12 content-type is "application/soap+xml"
337: *
338: * What about other content-types?
339: *
340: * TODO: I'm not fully convinced this method is complete, given the media types
341: * listed in HTTPConstants. Should we assume all application/* is SOAP12?
342: * Should we assume all text/* is SOAP11?
343: *
344: * So, we'll follow this pattern:
345: * 1) find the content-type main setting
346: * 2) if (1) not understood, find the "type=" param
347: * Thilina: I merged (1) & (2)
348: */
349:
350: if (JavaUtils.indexOfIgnoreCase(contentType,
351: SOAP12Constants.SOAP_12_CONTENT_TYPE) > -1) {
352: returnNS = SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI;
353: }
354: // search for "type=text/xml"
355: else if (JavaUtils.indexOfIgnoreCase(contentType,
356: SOAP11Constants.SOAP_11_CONTENT_TYPE) > -1) {
357: returnNS = SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
358: }
359: }
360:
361: if (returnNS == null) {
362: if (log.isDebugEnabled()) {
363: log
364: .debug("No content-type or \"type=\" parameter was found in the content-type "
365: + "header and no default was specified, thus defaulting to SOAP 1.1.");
366: }
367: returnNS = SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
368: }
369:
370: if (log.isDebugEnabled()) {
371: log.debug("content-type: " + contentType);
372: log.debug("defaultSOAPNamespace: " + defaultSOAPNamespace);
373: log.debug("Returned namespace: " + returnNS);
374: }
375: return returnNS;
376:
377: }
378:
379: public static void processContentTypeForAction(String contentType,
380: MessageContext msgContext) {
381: //Check for action header and set it in as soapAction in MessageContext
382: int index = contentType.indexOf("action");
383: if (index > -1) {
384: String transientString = contentType.substring(index,
385: contentType.length());
386: int equal = transientString.indexOf("=");
387: int firstSemiColon = transientString.indexOf(";");
388: String soapAction; // This will contain "" in the string
389: if (firstSemiColon > -1) {
390: soapAction = transientString.substring(equal + 1,
391: firstSemiColon);
392: } else {
393: soapAction = transientString.substring(equal + 1,
394: transientString.length());
395: }
396: if ((soapAction != null) && soapAction.startsWith("\"")
397: && soapAction.endsWith("\"")) {
398: soapAction = soapAction.substring(1, soapAction
399: .length() - 1);
400: }
401: msgContext.setSoapAction(soapAction);
402: }
403: }
404:
405: private static String getMessageFormatterProperty(
406: MessageContext msgContext) {
407: String messageFormatterProperty = null;
408: Object property = msgContext
409: .getProperty(Constants.Configuration.MESSAGE_TYPE);
410: if (property != null) {
411: messageFormatterProperty = (String) property;
412: }
413: if (messageFormatterProperty == null) {
414: Parameter parameter = msgContext
415: .getParameter(Constants.Configuration.MESSAGE_TYPE);
416: if (parameter != null) {
417: messageFormatterProperty = (String) parameter
418: .getValue();
419: }
420: }
421: return messageFormatterProperty;
422: }
423:
424: /**
425: * This is a helper method to get the response written flag from the RequestResponseTransport
426: * instance.
427: */
428: public static boolean isResponseWritten(
429: MessageContext messageContext) {
430: RequestResponseTransport reqResTransport = getRequestResponseTransport(messageContext);
431: if (reqResTransport != null) {
432: if (log.isDebugEnabled()) {
433: log
434: .debug("Found RequestResponseTransport returning isResponseWritten()");
435: }
436: return reqResTransport.isResponseWritten();
437: } else {
438: if (log.isDebugEnabled()) {
439: log
440: .debug("Did not find RequestResponseTransport returning false from get"
441: + "ResponseWritten()");
442: }
443: return false;
444: }
445: }
446:
447: /**
448: * This is a helper method to set the response written flag on the RequestResponseTransport
449: * instance.
450: */
451: public static void setResponseWritten(
452: MessageContext messageContext, boolean responseWritten) {
453: RequestResponseTransport reqResTransport = getRequestResponseTransport(messageContext);
454: if (reqResTransport != null) {
455: if (log.isDebugEnabled()) {
456: log
457: .debug("Found RequestResponseTransport setting response written");
458: }
459: reqResTransport.setResponseWritten(responseWritten);
460: } else {
461: if (log.isDebugEnabled()) {
462: log
463: .debug("Did not find RequestResponseTransport cannot set response written");
464: }
465: }
466: }
467:
468: /**
469: * This is an internal helper method to retrieve the RequestResponseTransport instance
470: * from the MessageContext object. The MessageContext may be the response MessageContext so
471: * in that case we will have to retrieve the request MessageContext from the OperationContext.
472: */
473: private static RequestResponseTransport getRequestResponseTransport(
474: MessageContext messageContext) {
475: try {
476: // If this is the request MessageContext we should find it directly by the getProperty()
477: // method
478: if (messageContext
479: .getProperty(RequestResponseTransport.TRANSPORT_CONTROL) != null) {
480: return (RequestResponseTransport) messageContext
481: .getProperty(RequestResponseTransport.TRANSPORT_CONTROL);
482: }
483: // If this is the response MessageContext we need to look for the request MessageContext
484: else if (messageContext.getOperationContext() != null
485: && messageContext
486: .getOperationContext()
487: .getMessageContext(
488: WSDLConstants.MESSAGE_LABEL_IN_VALUE) != null) {
489: return (RequestResponseTransport) messageContext
490: .getOperationContext()
491: .getMessageContext(
492: WSDLConstants.MESSAGE_LABEL_IN_VALUE)
493: .getProperty(
494: RequestResponseTransport.TRANSPORT_CONTROL);
495: } else {
496: return null;
497: }
498: } catch (AxisFault af) {
499: // probably should not be fatal, so just log the message
500: String msg = Messages.getMessage("getMessageContextError",
501: af.toString());
502: log.debug(msg);
503: return null;
504: }
505: }
506:
507: /**
508: * Clean up cached attachment file
509: * @param msgContext
510: */
511: public static void deleteAttachments(MessageContext msgContext) {
512: if (log.isDebugEnabled()) {
513: log.debug("Entering deleteAttachments()");
514: }
515:
516: Attachments attachments = msgContext.getAttachmentMap();
517: if (attachments != null) {
518: String[] keys = attachments.getAllContentIDs();
519: if (keys != null) {
520: String key = null;
521: File file = null;
522: DataSource dataSource = null;
523: for (int i = 0; i < keys.length; i++) {
524: try {
525: key = keys[i];
526: dataSource = attachments.getDataHandler(key)
527: .getDataSource();
528: if (dataSource instanceof CachedFileDataSource) {
529: file = ((CachedFileDataSource) dataSource)
530: .getFile();
531: if (log.isDebugEnabled()) {
532: log
533: .debug("Delete cache attachment file: "
534: + file.getName());
535: }
536: file.delete();
537: }
538: } catch (Exception e) {
539: if (file != null) {
540: file.deleteOnExit();
541: }
542: }
543: }
544: }
545: }
546:
547: if (log.isDebugEnabled()) {
548: log.debug("Exiting deleteAttachments()");
549: }
550: }
551: }
|