/**
* 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.util;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.Constants;
import org.apache.axis2.transport.TransportUtils;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.AxisOperation;
import org.apache.http.protocol.HTTP;
import org.apache.http.HttpStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.transport.passthru.PassThroughConstants;
import org.apache.synapse.transport.passthru.config.TargetConfiguration;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.NetworkInterface;
import java.util.Map;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* Utility methods used by the transport.
*/
public class PassThroughTransportUtils {
private static Log log = LogFactory.getLog(PassThroughTransportUtils.class);
/**
* This method tries to determine the hostname of the given InetAddress without
* triggering a reverse DNS lookup. {@link java.net.InetAddress#getHostName()}
* triggers a reverse DNS lookup which can be very costly in cases where reverse
* DNS fails. Tries to parse a symbolic hostname from {@link java.net.InetAddress#toString()},
* which is documented to return a String of the form "hostname / literal IP address"
* with 'hostname' blank if not already computed & stored in <code>address</code>.
* <p/>
* If the hostname cannot be determined from InetAddress.toString(),
* the value of {@link java.net.InetAddress#getHostAddress()} is returned.
*
* @param address The InetAddress whose hostname has to be determined
* @return hostsname, if it can be determined. hostaddress, if not.
*/
public static String getHostName(InetAddress address) {
String result;
String hostAddress = address.getHostAddress();
String inetAddr = address.toString();
int index1 = inetAddr.lastIndexOf('/');
int index2 = inetAddr.indexOf(hostAddress);
if (index2 == index1 + 1) {
if (index1 == 0) {
result = hostAddress;
} else {
result = inetAddr.substring(0, index1);
}
} else {
result = hostAddress;
}
return result;
}
/**
* Get the EPR for the message passed in
* @param msgContext the message context
* @return the destination EPR
*/
public static EndpointReference getDestinationEPR(MessageContext msgContext) {
// Trasnport URL can be different from the WSA-To
String transportURL = (String) msgContext.getProperty(
Constants.Configuration.TRANSPORT_URL);
if (transportURL != null) {
return new EndpointReference(transportURL);
} else if (
(msgContext.getTo() != null) && !msgContext.getTo().hasAnonymousAddress()) {
return msgContext.getTo();
}
return null;
}
/**
* Remove unwanted headers from the http response of outgoing request. These are headers which
* should be dictated by the transport and not the user. We remove these as these may get
* copied from the request messages
*
* @param msgContext the Axis2 Message context from which these headers should be removed
* @param targetConfiguration configuration for the passThrough handler
*/
public static void removeUnwantedHeaders(MessageContext msgContext, TargetConfiguration targetConfiguration) {
Map transportHeaders = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);
Map excessHeaders = (Map) msgContext.getProperty(NhttpConstants.EXCESS_TRANSPORT_HEADERS);
if (transportHeaders != null && !transportHeaders.isEmpty()) {
//a hack which takes the original content header
if (transportHeaders.get(HTTP.CONTENT_LEN) != null) {
msgContext.setProperty(PassThroughConstants.ORGINAL_CONTEN_LENGTH,
transportHeaders.get(HTTP.CONTENT_LEN));
}
removeUnwantedHeadersFromHeaderMap(transportHeaders, targetConfiguration);
}
if (excessHeaders != null && !excessHeaders.isEmpty()) {
removeUnwantedHeadersFromHeaderMap(excessHeaders, targetConfiguration);
}
}
/**
* Remove unwanted headers from the given header map.
*
* @param headers http header map
* @param targetConfiguration configurations for the passThrough transporter
*/
private static void removeUnwantedHeadersFromHeaderMap(Map headers,
TargetConfiguration targetConfiguration) {
Iterator iter = headers.keySet().iterator();
while (iter.hasNext()) {
String headerName = (String) iter.next();
if (HTTP.CONN_DIRECTIVE.equalsIgnoreCase(headerName) ||
HTTP.TRANSFER_ENCODING.equalsIgnoreCase(headerName)) {
iter.remove();
}
if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(headerName)
&& !targetConfiguration.isPreserveHttpHeader(HTTP.CONN_KEEP_ALIVE)) {
iter.remove();
}
if (HTTP.CONTENT_LEN.equalsIgnoreCase(headerName)
&& !targetConfiguration.isPreserveHttpHeader(HTTP.CONTENT_LEN)) {
iter.remove();
}
if (HTTP.DATE_HEADER.equalsIgnoreCase(headerName)
&& !targetConfiguration.isPreserveHttpHeader(HTTP.DATE_HEADER)) {
iter.remove();
}
if (HTTP.SERVER_HEADER.equalsIgnoreCase(headerName)
&& !targetConfiguration.isPreserveHttpHeader(HTTP.SERVER_HEADER)) {
iter.remove();
}
if (HTTP.USER_AGENT.equalsIgnoreCase(headerName)
&& !targetConfiguration.isPreserveHttpHeader(HTTP.USER_AGENT)) {
iter.remove();
}
}
}
/**
* Determine the Http Status Code depending on the message type processed <br>
* (normal response versus fault response) as well as Axis2 message context properties set
* via Synapse configuration or MessageBuilders.
*
* @see PassThroughConstants#FAULTS_AS_HTTP_200
* @see PassThroughConstants#HTTP_SC
*
* @param msgContext the Axis2 message context
*
* @return the HTTP status code to set in the HTTP response object
*/
public static int determineHttpStatusCode(MessageContext msgContext) {
int httpStatus = HttpStatus.SC_OK;
// if this is a dummy message to handle http 202 case with non-blocking IO
// set the status code to 202
if (msgContext.isPropertyTrue(PassThroughConstants.SC_ACCEPTED)) {
httpStatus = HttpStatus.SC_ACCEPTED;
} else {
// is this a fault message
boolean handleFault = msgContext.getEnvelope() != null ?
(msgContext.getEnvelope().getBody().hasFault() || msgContext.isProcessingFault()):false;
boolean faultsAsHttp200 = false;
if (msgContext.getProperty(PassThroughConstants.FAULTS_AS_HTTP_200) != null) {
// shall faults be transmitted with HTTP 200
faultsAsHttp200 =
PassThroughConstants.TRUE.equals(
msgContext.getProperty(PassThroughConstants.FAULTS_AS_HTTP_200).toString().toUpperCase());
}
// Set HTTP status code to 500 if this is a fault case and we shall not use HTTP 200
if (handleFault && !faultsAsHttp200) {
httpStatus = HttpStatus.SC_INTERNAL_SERVER_ERROR;
}
// Any status code previously set shall be overwritten with the value of the following
// message context property if it is set.
Object statusCode = msgContext.getProperty(PassThroughConstants.HTTP_SC);
if (statusCode != null) {
try {
httpStatus = Integer.parseInt(
msgContext.getProperty(PassThroughConstants.HTTP_SC).toString());
} catch (NumberFormatException e) {
log.warn("Unable to set the HTTP status code from the property "
+ PassThroughConstants.HTTP_SC + " with value: " + statusCode);
}
}
}
return httpStatus;
}
/**
* Determine the Http Status Message depending on the message type processed <br>
* (normal response versus fault response) as well as Axis2 message context properties set
* via Synapse configuration or MessageBuilders.
*
* @see PassThroughConstants#FAULTS_AS_HTTP_200
* @see PassThroughConstants#HTTP_SC
*
* @param msgContext the Axis2 message context
*
* @return the HTTP status message string or null
*/
public static String determineHttpStatusLine(MessageContext msgContext) {
Object statusLine = msgContext.getProperty(PassThroughConstants.HTTP_SC_DESC);
if (statusLine != null) {
return (String) statusLine;
}
return null;
}
/**
* Whatever this method returns as the IP is ignored by the actual http/s listener when
* its getServiceEPR is invoked. This was originally copied from axis2
*
* @return Returns String.
* @throws java.net.SocketException if the socket can not be accessed
*/
public static String getIpAddress() throws SocketException {
Enumeration e = NetworkInterface.getNetworkInterfaces();
String address = "127.0.0.1";
while (e.hasMoreElements()) {
NetworkInterface netface = (NetworkInterface) e.nextElement();
Enumeration addresses = netface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress ip = (InetAddress) addresses.nextElement();
if (!ip.isLoopbackAddress() && isIP(ip.getHostAddress())) {
return ip.getHostAddress();
}
}
}
return address;
}
private static boolean isIP(String hostAddress) {
return hostAddress.split("[.]").length == 4;
}
/**
* Returns the HTML text for the list of services deployed.
* This can be delegated to another Class as well
* where it will handle more options of GET messages.
*
* @param prefix to be used for the Service names
* @param cfgCtx axis2 configuration context
* @return the HTML to be displayed as a String
*/
public String getServicesHTML(String prefix, ConfigurationContext cfgCtx) {
Map services = cfgCtx.getAxisConfiguration().getServices();
Hashtable erroneousServices = cfgCtx.getAxisConfiguration().getFaultyServices();
boolean servicesFound = false;
StringBuffer resultBuf = new StringBuffer();
resultBuf.append("<html><head><title>Axis2: Services</title></head>" + "<body>");
if ((services != null) && !services.isEmpty()) {
servicesFound = true;
resultBuf.append("<h2>" + "Deployed services" + "</h2>");
for (Object service : services.values()) {
AxisService axisService = (AxisService) service;
Parameter parameter = axisService.getParameter(
PassThroughConstants.HIDDEN_SERVICE_PARAM_NAME);
if (axisService.getName().startsWith("__") ||
(parameter != null && JavaUtils.isTrueExplicitly(parameter.getValue()))) {
continue; // skip private services
}
Iterator iterator = axisService.getOperations();
resultBuf.append("<h3><a href=\"").append(prefix).append(axisService.getName()).append(
"?wsdl\">").append(axisService.getName()).append("</a></h3>");
if (iterator.hasNext()) {
resultBuf.append("Available operations <ul>");
for (; iterator.hasNext();) {
AxisOperation axisOperation = (AxisOperation) iterator.next();
resultBuf.append("<li>").append(
axisOperation.getName().getLocalPart()).append("</li>");
}
resultBuf.append("</ul>");
} else {
resultBuf.append("No operations specified for this service");
}
}
}
if ((erroneousServices != null) && !erroneousServices.isEmpty()) {
servicesFound = true;
resultBuf.append("<hr><h2><font color=\"blue\">Faulty Services</font></h2>");
Enumeration faultyservices = erroneousServices.keys();
while (faultyservices.hasMoreElements()) {
String faultyserviceName = (String) faultyservices.nextElement();
resultBuf.append("<h3><font color=\"blue\">").append(
faultyserviceName).append("</font></h3>");
}
}
if (!servicesFound) {
resultBuf.append("<h2>There are no services deployed</h2>");
}
resultBuf.append("</body></html>");
return resultBuf.toString();
}
public static OMOutputFormat getOMOutputFormat(MessageContext msgContext) {
OMOutputFormat format = null;
if(msgContext.getProperty(PassThroughConstants.MESSAGE_OUTPUT_FORMAT) != null){
format = (OMOutputFormat) msgContext.getProperty(PassThroughConstants.MESSAGE_OUTPUT_FORMAT);
}else{
format = new OMOutputFormat();
}
msgContext.setDoingMTOM(TransportUtils.doWriteMTOM(msgContext));
msgContext.setDoingSwA(TransportUtils.doWriteSwA(msgContext));
msgContext.setDoingREST(TransportUtils.isDoingREST(msgContext));
/**
* PassThroughConstants.INVOKED_REST set to true here if isDoingREST is true -
* this enables us to check whether the original request to the endpoint was a
* REST request inside DefferedMessageBuilder (which we need to convert
* text/xml content type into application/xml if the request was not a SOAP
* request.
*/
if(msgContext.isDoingREST()) {
msgContext.setProperty(PassThroughConstants.INVOKED_REST, true);
}
format.setSOAP11(msgContext.isSOAP11());
format.setDoOptimize(msgContext.isDoingMTOM());
format.setDoingSWA(msgContext.isDoingSwA());
format.setCharSetEncoding(TransportUtils.getCharSetEncoding(msgContext));
Object mimeBoundaryProperty = msgContext.getProperty(Constants.Configuration.MIME_BOUNDARY);
if (mimeBoundaryProperty != null) {
format.setMimeBoundary((String) mimeBoundaryProperty);
}
return format;
}
}