/**
* Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.AddressingConstants;
import org.apache.axis2.builder.BuilderUtil;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.WSDL2Constants;
import org.apache.axis2.engine.AxisEngine;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.apache.http.nio.NHttpServerConnection;
import org.apache.http.protocol.HTTP;
import org.apache.synapse.commons.util.ext.TenantInfoInitiator;
import org.apache.synapse.commons.util.ext.TenantInfoInitiatorProvider;
import org.apache.synapse.transport.customlogsetter.CustomLogSetter;
import org.apache.synapse.transport.http.conn.SynapseDebugInfoHolder;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.transport.passthru.config.TargetConfiguration;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class ClientWorker implements Runnable {
private Log log = LogFactory.getLog(ClientWorker.class);
/** the Http connectors configuration context */
private TargetConfiguration targetConfiguration = null;
/** the response message context that would be created */
private org.apache.axis2.context.MessageContext responseMsgCtx = null;
/** the HttpResponse received */
private TargetResponse response = null;
/** weather a body is expected or not */
private boolean expectEntityBody = true;
public ClientWorker(TargetConfiguration targetConfiguration,
MessageContext outMsgCtx,
TargetResponse response) {
this.targetConfiguration = targetConfiguration;
this.response = response;
this.expectEntityBody = response.isExpectResponseBody();
Map<String,String> headers = response.getHeaders();
Map excessHeaders = response.getExcessHeaders();
String oriURL = headers.get(PassThroughConstants.LOCATION);
if (outMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONNECTION) != null) {
((NHttpServerConnection) outMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONNECTION)).
getContext().setAttribute(PassThroughConstants.CLIENT_WORKER_INIT_TIME, System.currentTimeMillis());
}
// Special casing 301, 302, 303 and 307 scenario in following section. Not sure whether it's the correct fix,
// but this fix makes it possible to do http --> https redirection.
if (oriURL != null && ((response.getStatus() != HttpStatus.SC_MOVED_TEMPORARILY) &&
(response.getStatus() != HttpStatus.SC_MOVED_PERMANENTLY) &&
(response.getStatus() != HttpStatus.SC_CREATED) &&
(response.getStatus() != HttpStatus.SC_SEE_OTHER) &&
(response.getStatus() != HttpStatus.SC_TEMPORARY_REDIRECT) &&
!targetConfiguration.isPreserveHttpHeader(PassThroughConstants.LOCATION))) {
URL url;
String urlContext = null;
try {
url = new URL(oriURL);
urlContext = url.getFile();
} catch (MalformedURLException e) {
//Fix ESBJAVA-3461 - In the case when relative path is sent should be handled
if(log.isDebugEnabled()){
log.debug("Relative URL received for Location : " + oriURL, e);
}
urlContext = oriURL;
}
headers.remove(PassThroughConstants.LOCATION);
String prfix = (String) outMsgCtx.getProperty(PassThroughConstants.SERVICE_PREFIX);
if (prfix != null) {
if(urlContext != null && urlContext.startsWith("/")){
//Remove the preceding '/' character
urlContext = urlContext.substring(1);
}
headers.put(PassThroughConstants.LOCATION, prfix + urlContext);
}
}
try {
responseMsgCtx = outMsgCtx.getOperationContext().
getMessageContext(WSDL2Constants.MESSAGE_LABEL_IN);
// fix for RM to work because of a soapAction and wsaAction conflict
if (responseMsgCtx != null) {
responseMsgCtx.setSoapAction("");
}
} catch (AxisFault af) {
log.error("Error getting IN message context from the operation context", af);
return;
}
if (responseMsgCtx == null) {
if (outMsgCtx.getOperationContext().isComplete()) {
if (log.isDebugEnabled()) {
log.debug("Error getting IN message context from the operation context. " +
"Possibly an RM terminate sequence message");
}
return;
}
responseMsgCtx = new MessageContext();
responseMsgCtx.setOperationContext(outMsgCtx.getOperationContext());
}
responseMsgCtx.setProperty("PRE_LOCATION_HEADER",oriURL);
// copy the important properties from the original message context
responseMsgCtx.setProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONNECTION,
outMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONNECTION));
responseMsgCtx.setProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONFIGURATION,
outMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONFIGURATION));
responseMsgCtx.setServerSide(true);
responseMsgCtx.setDoingREST(outMsgCtx.isDoingREST());
responseMsgCtx.setProperty(MessageContext.TRANSPORT_IN, outMsgCtx
.getProperty(MessageContext.TRANSPORT_IN));
responseMsgCtx.setTransportIn(outMsgCtx.getTransportIn());
responseMsgCtx.setTransportOut(outMsgCtx.getTransportOut());
//setting the responseMsgCtx PassThroughConstants.INVOKED_REST property to the one set inside PassThroughTransportUtils
responseMsgCtx.setProperty(PassThroughConstants.INVOKED_REST, outMsgCtx.isDoingREST());
responseMsgCtx.setProperty(PassThroughConstants.ORIGINAL_HTTP_SC, response.getStatus());
responseMsgCtx.setProperty(PassThroughConstants.ORIGINAL_HTTP_REASON_PHRASE, response.getStatusLine());
// set any transport headers received
Set<Map.Entry<String, String>> headerEntries = response.getHeaders().entrySet();
Map<String, String> headerMap = new TreeMap<String, String>(new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
for (Map.Entry<String, String> headerEntry : headerEntries) {
headerMap.put(headerEntry.getKey(), headerEntry.getValue());
}
responseMsgCtx.setProperty(MessageContext.TRANSPORT_HEADERS, headerMap);
responseMsgCtx.setProperty(NhttpConstants.EXCESS_TRANSPORT_HEADERS, excessHeaders);
if (response.getStatus() == 202) {
responseMsgCtx.setProperty(AddressingConstants.
DISABLE_ADDRESSING_FOR_OUT_MESSAGES, Boolean.TRUE);
responseMsgCtx.setProperty(PassThroughConstants.MESSAGE_BUILDER_INVOKED, Boolean.FALSE);
responseMsgCtx.setProperty(NhttpConstants.SC_ACCEPTED, Boolean.TRUE);
}
responseMsgCtx.setAxisMessage(outMsgCtx.getOperationContext().getAxisOperation().
getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE));
responseMsgCtx.setOperationContext(outMsgCtx.getOperationContext());
responseMsgCtx.setConfigurationContext(outMsgCtx.getConfigurationContext());
responseMsgCtx.setTo(null);
responseMsgCtx.setProperty(PassThroughConstants.PASS_THROUGH_PIPE, response.getPipe());
responseMsgCtx.setProperty(PassThroughConstants.PASS_THROUGH_TARGET_RESPONSE, response);
responseMsgCtx.setProperty(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_HOLDER_PROPERTY, response.getConnection()
.getContext().getAttribute(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_HOLDER_PROPERTY));
responseMsgCtx.setProperty(PassThroughConstants.PASS_THROUGH_TARGET_CONNECTION,
response.getConnection());
}
public void run() {
CustomLogSetter.getInstance().clearThreadLocalContent();
TenantInfoInitiator tenantInfoInitiator = TenantInfoInitiatorProvider.getTenantInfoInitiator();
if (tenantInfoInitiator != null) {
tenantInfoInitiator.initTenantInfo();
}
if (responseMsgCtx == null) {
return;
}
if (responseMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONNECTION) != null) {
((NHttpServerConnection) responseMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_SOURCE_CONNECTION)).
getContext().setAttribute(PassThroughConstants.CLIENT_WORKER_START_TIME, System.currentTimeMillis());
}
try {
if (expectEntityBody) {
String cType = response.getHeader(HTTP.CONTENT_TYPE);
if(cType == null){
cType = response.getHeader(HTTP.CONTENT_TYPE.toLowerCase());
}
String contentType;
if (cType != null) {
// This is the most common case - Most of the time servers send the Content-Type
contentType = cType;
} else {
// Server hasn't sent the header - Try to infer the content type
contentType = inferContentType();
}
responseMsgCtx.setProperty(Constants.Configuration.CONTENT_TYPE, contentType);
String charSetEnc = BuilderUtil.getCharSetEncoding(contentType);
if (charSetEnc == null) {
charSetEnc = MessageContext.DEFAULT_CHAR_SET_ENCODING;
}
if (contentType != null) {
responseMsgCtx.setProperty(
Constants.Configuration.CHARACTER_SET_ENCODING,
contentType.indexOf("charset") > 0 ?
charSetEnc : MessageContext.DEFAULT_CHAR_SET_ENCODING);
}
responseMsgCtx.setServerSide(false);
SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();
SOAPEnvelope envelope = fac.getDefaultEnvelope();
try {
responseMsgCtx.setEnvelope(envelope);
} catch (AxisFault axisFault) {
log.error("Error setting SOAP envelope", axisFault);
}
responseMsgCtx.setServerSide(true);
responseMsgCtx.removeProperty(PassThroughConstants.NO_ENTITY_BODY);
} else {
// there is no response entity-body
responseMsgCtx.setProperty(PassThroughConstants.NO_ENTITY_BODY, Boolean.TRUE);
responseMsgCtx.setEnvelope(new SOAP11Factory().getDefaultEnvelope());
}
// copy the HTTP status code as a message context property with the key HTTP_SC to be
// used at the sender to set the proper status code when passing the message
int statusCode = this.response.getStatus();
responseMsgCtx.setProperty(PassThroughConstants.HTTP_SC, statusCode);
responseMsgCtx.setProperty(PassThroughConstants.HTTP_SC_DESC, response.getStatusLine());
if (statusCode >= 400) {
responseMsgCtx.setProperty(PassThroughConstants.FAULT_MESSAGE,
PassThroughConstants.TRUE);
} /*else if (statusCode == 202 && responseMsgCtx.getOperationContext().isComplete()) {
// Handle out-only invocation scenario
responseMsgCtx.setProperty(PassThroughConstants.MESSAGE_BUILDER_INVOKED, Boolean.TRUE);
}*/
responseMsgCtx.setProperty(PassThroughConstants.NON_BLOCKING_TRANSPORT, true);
// process response received
try {
AxisEngine.receive(responseMsgCtx);
} catch (AxisFault af) {
log.error("Fault processing response message through Axis2", af);
}
} catch (AxisFault af) {
log.error("Fault creating response SOAP envelope", af);
}
}
private String inferContentType() {
//Check whether server sent Content-Type in different case
Map<String,String> headers = response.getHeaders();
for(String header : headers.keySet()){
if(HTTP.CONTENT_TYPE.equalsIgnoreCase(header)){
return headers.get(header);
}
}
String cType = response.getHeader("content-type");
if (cType != null) {
return cType;
}
cType = response.getHeader("Content-type");
if (cType != null) {
return cType;
}
// Try to get the content type from the message context
Object cTypeProperty = responseMsgCtx.getProperty(PassThroughConstants.CONTENT_TYPE);
if (cTypeProperty != null) {
return cTypeProperty.toString();
}
// Try to get the content type from the axis configuration
Parameter cTypeParam = targetConfiguration.getConfigurationContext().getAxisConfiguration().getParameter(
PassThroughConstants.CONTENT_TYPE);
if (cTypeParam != null) {
return cTypeParam.getValue().toString();
}
// When the response from backend does not have the body(Content-Length is 0 )
// and Content-Type is not set; ESB should not do any modification to the response and pass-through as it is.
if (headers.get(HTTP.CONTENT_LEN) == null || "0".equals(headers.get(HTTP.CONTENT_LEN))) {
return null;
}
// Unable to determine the content type - Return default value
return PassThroughConstants.DEFAULT_CONTENT_TYPE;
}
}