/*
* Copyright 2005,2006 WSO2, Inc. http://www.wso2.org
*
* 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.apache.synapse.transport.passthru.util;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.impl.dom.soap11.SOAP11Factory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.builder.*;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.transport.http.*;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.protocol.HTTP;
import org.apache.synapse.transport.passthru.PassThroughConstants;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class DeferredMessageBuilder {
private static Log log = LogFactory.getLog(DeferredMessageBuilder.class);
private Map<String, Builder> builders = new HashMap<String, Builder>();
private Map<String, MessageFormatter> formatters = new HashMap<String, MessageFormatter>();
public final static String RELAY_FORMATTERS_MAP = "__RELAY_FORMATTERS_MAP";
public final static String FORCED_RELAY_FORMATTER = "__FORCED_RELAY_FORMATTER";
public DeferredMessageBuilder() {
// first initialize with the default builders
builders.put("multipart/related", new MIMEBuilder());
builders.put("application/soap+xml", new SOAPBuilder());
builders.put("text/xml", new SOAPBuilder());
builders.put("application/xop+xml", new MTOMBuilder());
builders.put("application/xml", new ApplicationXMLBuilder());
builders.put("application/x-www-form-urlencoded",
new XFormURLEncodedBuilder());
// initialize the default formatters
formatters.put("application/x-www-form-urlencoded", new XFormURLEncodedFormatter());
formatters.put("multipart/form-data", new MultipartFormDataFormatter());
formatters.put("application/xml", new ApplicationXMLFormatter());
formatters.put("text/xml", new SOAPMessageFormatter());
formatters.put("application/soap+xml", new SOAPMessageFormatter());
}
public Map<String, Builder> getBuilders() {
return builders;
}
public void addBuilder(String contentType, Builder builder) {
builders.put(contentType, builder);
}
public void addFormatter(String contentType, MessageFormatter messageFormatter) {
formatters.put(contentType, messageFormatter);
}
public Map<String, MessageFormatter> getFormatters() {
return formatters;
}
public OMElement getDocument(MessageContext msgCtx, InputStream in) throws
XMLStreamException, IOException {
String contentType = (String) msgCtx.getProperty(Constants.Configuration.CONTENT_TYPE);
String _contentType = getContentType(contentType, msgCtx);
in = HTTPTransportUtils.handleGZip(msgCtx, in);
AxisConfiguration configuration = msgCtx.getConfigurationContext().getAxisConfiguration();
Parameter useFallbackParameter =
configuration.getParameter(Constants.Configuration.USE_DEFAULT_FALLBACK_BUILDER);
boolean useFallbackBuilder = false;
if (useFallbackParameter != null) {
useFallbackBuilder =
JavaUtils.isTrueExplicitly(useFallbackParameter.getValue(),
useFallbackBuilder);
}
Map transportHeaders = (Map) msgCtx.getProperty(MessageContext.TRANSPORT_HEADERS);
String contentLength = null;
String trasferEncoded = null;
if (transportHeaders != null) {
contentLength = (String) transportHeaders.get(HTTP.CONTENT_LEN);
trasferEncoded = (String) transportHeaders.get(HTTP.TRANSFER_ENCODING);
if (contentType.equals(PassThroughConstants.DEFAULT_CONTENT_TYPE)
&& (contentLength == null || Integer.valueOf(contentLength) == 0)
&& trasferEncoded == null) {
msgCtx.setProperty(PassThroughConstants.NO_ENTITY_BODY, true);
msgCtx.setProperty(Constants.Configuration.CONTENT_TYPE, "");
msgCtx.setProperty(PassThroughConstants.RELAY_EARLY_BUILD, true);
return new SOAP11Factory().getDefaultEnvelope();
}
}
OMElement element = null;
Builder builder;
if (contentType != null) {
// loading builder from externally..
//builder = configuration.getMessageBuilder(_contentType,useFallbackBuilder);
builder = MessageProcessorSelector.getMessageBuilder(_contentType, msgCtx);
if (builder != null) {
try {
/*try {
throw new Exception("Building message");
} catch (Exception e) {
e.printStackTrace();
}*/
if (contentLength != null && "0".equals(contentLength)) {
element = new org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory().getDefaultEnvelope();
//since we are setting an empty envelop to achieve the empty body, we have to set a different
//content-type other than text/xml, application/soap+xml or any other content-type which will
//invoke the soap builder, otherwise soap builder will get hit and an empty envelope
// will be send out
msgCtx.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/xml");
} else {
element = builder.processDocument(in, contentType, msgCtx);
}
} catch (AxisFault axisFault) {
log.error("Error building message", axisFault);
throw axisFault;
}
}
}
if (element == null) {
if (msgCtx.isDoingREST()) {
try {
element = BuilderUtil.getPOXBuilder(in, null).getDocumentElement();
} catch (XMLStreamException e) {
log.error("Error building message using POX Builder", e);
throw e;
}
} else {
// switch to default
builder = new SOAPBuilder();
try {
if (contentLength != null && "0".equals(contentLength)) {
element = new SOAP11Factory().getDefaultEnvelope();
//since we are setting an empty envelop to achieve the empty body, we have to set a different
//content-type other than text/xml, application/soap+xml or any other content-type which will
//invoke the soap builder, otherwise soap builder will get hit and an empty envelope
// will be send out
msgCtx.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/xml");
} else {
element = builder.processDocument(in, contentType, msgCtx);
}
} catch (AxisFault axisFault) {
log.error("Error building message using SOAP builder");
throw axisFault;
}
}
}
// build the soap headers and body
if (element instanceof SOAPEnvelope) {
SOAPEnvelope env = (SOAPEnvelope) element;
env.hasFault();
}
//setting up original contentType (resetting the content type)
if(contentType != null && !contentType.isEmpty()){
msgCtx.setProperty(Constants.Configuration.CONTENT_TYPE, contentType);
}
return element;
}
private Builder getBuilderForContentType(String contentType) {
String type;
int index = contentType.indexOf(';');
if (index > 0) {
type = contentType.substring(0, index);
} else {
type = contentType;
}
Builder builder = builders.get(type);
if (builder == null) {
builder = builders.get(type.toLowerCase());
}
if (builder == null) {
Iterator<Map.Entry<String, Builder>> iterator = builders.entrySet().iterator();
while (iterator.hasNext() && builder == null) {
Map.Entry<String, Builder> entry = iterator.next();
String key = entry.getKey();
if (contentType.matches(key)) {
builder = entry.getValue();
}
}
}
return builder;
}
public static Builder createBuilder(String className) throws AxisFault {
try {
Class c = Class.forName(className);
Object o = c.newInstance();
if (o instanceof Builder) {
return (Builder) o;
}
} catch (ClassNotFoundException e) {
handleException("Builder class not found :" +
className, e);
} catch (IllegalAccessException e) {
handleException("Cannot initiate Builder class :" +
className, e);
} catch (InstantiationException e) {
handleException("Cannot initiate Builder class :" +
className, e);
}
return null;
}
public static MessageFormatter createFormatter(String className) throws AxisFault {
try {
Class c = Class.forName(className);
Object o = c.newInstance();
if (o instanceof MessageFormatter) {
return (MessageFormatter) o;
}
} catch (ClassNotFoundException e) {
handleException("MessageFormatter class not found :" +
className, e);
} catch (IllegalAccessException e) {
handleException("Cannot initiate MessageFormatter class :" +
className, e);
} catch (InstantiationException e) {
handleException("Cannot initiate MessageFormatter class :" +
className, e);
}
return null;
}
private static void handleException(String message, Exception e) throws AxisFault {
log.error(message, e);
throw new AxisFault(message, e);
}
/**
* This method is from org.apache.axis2.transport.TransportUtils - it was hack placed in Axis2 Transport to enable
* responses with text/xml to be processed using the ApplicationXMLBuilder (which is technically wrong, it should be
* the duty of the backend service to send the correct content type, which makes the most sense (refer RFC 1049),
* alas, tis not the way of the World).
* @param contentType
* @param msgContext
* @return MIME content type.
*/
public static String getContentType(String contentType, MessageContext msgContext) {
String type;
int index = contentType.indexOf(';');
if (index > 0) {
type = contentType.substring(0, index);
} else {
int commaIndex = contentType.indexOf(',');
if (commaIndex > 0) {
type = contentType.substring(0, commaIndex);
} else {
type = contentType;
}
}
// Some services send REST responses as text/xml. We should convert it to
// application/xml if its a REST response, if not it will try to use the SOAPMessageBuilder.
// isDoingREST should already be properly set by HTTPTransportUtils.initializeMessageContext
if (null != msgContext.getProperty(PassThroughConstants.INVOKED_REST)
&& msgContext.getProperty(PassThroughConstants.INVOKED_REST).equals(true)
&& HTTPConstants.MEDIA_TYPE_TEXT_XML.equals(type)) {
type = HTTPConstants.MEDIA_TYPE_APPLICATION_XML;
}
return type;
}
}