/*
* JBoss, Home of Professional Open Source
* Copyright 2007, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006,
* @author JBoss Inc.
*/
/*
* Copyright (c) 2002, 2003, Arjuna Technologies Limited.
*
* $Id: DummyListenerService.java,v 1.2 2004/06/24 13:52:53 nmcl Exp $
*/
package com.jboss.transaction.txinterop.proxy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jboss.jbossts.xts.environment.WSCEnvironmentBean;
import org.jboss.jbossts.xts.environment.XTSPropertyManager;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
public class ProxyListenerService extends HttpServlet
{
/**
* The SOAP 1.1 content type.
*/
private static final String SOAP_11_CONTENT_TYPE = "text/xml" ;
/**
* The SOAP 1.2 content type.
*/
private static final String SOAP_12_CONTENT_TYPE = "application/soap+xml" ;
/**
* The name of the SOAP Action header.
*/
public static final String SOAP_ACTION_HEADER = "SOAPAction" ;
/**
* The name of the Transfer encoding header.
*/
public static final String TRANSFER_ENCODING_HEADER = "transfer-encoding" ;
/**
* The name of the SOAP Action header.
*/
public static final String TRANSFER_ENCODING_VALUE_CHUNKED = "chunked" ;
/**
* The default data size.
*/
private static final int DEFAULT_DATA_SIZE = 256 ;
/**
* Initialise the servlet.
* @param config The servlet configuration.
*/
public void init(final ServletConfig config)
throws ServletException
{
super.init(config);
// Initialise the local host:port/urlstub for the proxy.
WSCEnvironmentBean wscEnvironmentBean = XTSPropertyManager.getWSCEnvironmentBean();
String bindAddress = wscEnvironmentBean.getBindAddress11();
int bindPort = wscEnvironmentBean.getBindPort11();
String baseURI = "http://" + bindAddress + ":" + bindPort + "/interop11";
final String proxyServiceURI = baseURI + "/proxy";
ProxyURIRewriting.setProxyURI(proxyServiceURI) ;
}
/**
* Handle the post request.
* @param httpServletRequest The current HTTP servlet request.
* @param httpServletResponse The current HTTP servlet response.
*/
public void doPost(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse)
throws ServletException, IOException
{
final String conversationIdentifier = getConversationIdentifier(httpServletRequest) ;
final byte[] requestContents = getContents(httpServletRequest.getInputStream()) ;
final String soapAction = httpServletRequest.getHeader(SOAP_ACTION_HEADER) ;
final boolean jbossClient = ProxyConversation.isInternalConversationId(conversationIdentifier) ;
final String alternateConversationIdentifier = ProxyConversation.getAlternateConversationId(conversationIdentifier) ;
final ProxyConversationState state = ProxyConversation.getConversationState(conversationIdentifier) ;
System.out.println("KEV: processing SOAP action " + trimAction(soapAction)) ;
// Search header for wsa:To and wsa:Address elements, changing their URL parts as we go.
// Get the target URL from the to.
try
{
final StringWriter newMessageWriter = new StringWriter() ;
final WriterSAXHandler writerHandler = new WriterSAXHandler(newMessageWriter) ;
final AddressingProxySAXHandler addressingHandler = new AddressingProxySAXHandler(writerHandler, alternateConversationIdentifier) ;
ContentHandler stateHandler = (state == null ? null : state.getHandler(addressingHandler)) ;
ContentHandler parserHandler = (stateHandler == null ? addressingHandler : stateHandler) ;
final XMLReader xmlReader = XMLReaderFactory.createXMLReader() ;
xmlReader.setContentHandler(parserHandler) ;
xmlReader.parse(new InputSource(new ByteArrayInputStream(requestContents))) ;
final StringBuffer newMessageBuffer = newMessageWriter.getBuffer() ;
final StringBuffer messageBuffer = (jbossClient ? newMessageBuffer : new StringBuffer(new String(requestContents))) ;
ProxyConversation.appendConversation(conversationIdentifier, checkForXMLDecl(messageBuffer)) ;
final String identifier = addressingHandler.getIdentifier() ;
if ((state != null) && state.handleAction(trimAction(soapAction), identifier))
{
httpServletResponse.setStatus(HttpServletResponse.SC_ACCEPTED) ;
httpServletResponse.flushBuffer() ;
System.out.println("KEV: handled SOAP action " + trimAction(soapAction)) ;
return ;
}
// parse input stream
final URL destURL = new URL(addressingHandler.getToAddress()) ;
final HttpURLConnection destConnection = (HttpURLConnection)destURL.openConnection() ;
try
{
destConnection.setDoOutput(true) ;
destConnection.setUseCaches(false) ;
// copy the headers
final Enumeration headerNameEnum = httpServletRequest.getHeaderNames() ;
while(headerNameEnum.hasMoreElements())
{
final String name = (String)headerNameEnum.nextElement() ;
if (name.equalsIgnoreCase(TRANSFER_ENCODING_HEADER))
{
// any messages we send are not chunked!
final String value = httpServletRequest.getHeader(name) ;
if (!value.equalsIgnoreCase(TRANSFER_ENCODING_VALUE_CHUNKED))
{
// this may actually cause a problem depending upon the encoding but try it anyway
destConnection.setRequestProperty(name, value) ;
}
}
else
{
final String value = httpServletRequest.getHeader(name) ;
destConnection.setRequestProperty(name, value) ;
}
}
// Set content length
destConnection.setRequestProperty("Content-Length", Integer.toString(newMessageBuffer.length())) ;
final int port = destURL.getPort() ;
final String host = (port > 0 ? destURL.getHost() + ":" + port : destURL.getHost()) ;
destConnection.setRequestProperty("Host", host) ;
destConnection.setRequestMethod("POST") ;
// Connect
destConnection.connect() ;
// Write the new request
final OutputStream os = destConnection.getOutputStream() ;
os.write(newMessageBuffer.toString().getBytes()) ;
os.flush() ;
os.close() ;
final int responseCode = destConnection.getResponseCode() ;
final String fullContentType = destConnection.getContentType() ;
final String contentType = getContentType(fullContentType) ;
switch (responseCode)
{
case HttpServletResponse.SC_OK:
case HttpServletResponse.SC_ACCEPTED:
if ((contentType != null) && !(SOAP_11_CONTENT_TYPE.equals(contentType) ||
SOAP_12_CONTENT_TYPE.equals(contentType)))
{
httpServletResponse.setStatus(HttpServletResponse.SC_ACCEPTED) ;
httpServletResponse.flushBuffer() ;
break ;
}
// FALL THRU
default:
// Pass the response back.
httpServletResponse.setStatus(destConnection.getResponseCode()) ;
if (fullContentType != null)
{
httpServletResponse.setContentType(fullContentType) ;
}
// Copy data
final int datasize = DEFAULT_DATA_SIZE ;
final char[] data = new char[datasize] ;
int readCount ;
final InputStream is ;
if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR)
{
is = destConnection.getErrorStream() ;
}
else
{
is = destConnection.getInputStream() ;
}
if (is != null) {
final byte[] responseBytes = getContents(is) ;
if (responseBytes.length > 0) {
try
{
final StringWriter newResponseWriter = new StringWriter() ;
final WriterSAXHandler responseWriterHandler = new WriterSAXHandler(newResponseWriter) ;
final AddressingProxySAXHandler responseAddressingHandler = new AddressingProxySAXHandler(responseWriterHandler, alternateConversationIdentifier) ;
// refetch the state handler so it gets a chance to go away
stateHandler = (state == null ? null : state.getHandler(responseAddressingHandler)) ;
parserHandler = (stateHandler == null ? responseAddressingHandler : stateHandler) ;
// always use the standard handler for replies?
final XMLReader responseXmlReader = XMLReaderFactory.createXMLReader() ;
responseXmlReader.setContentHandler(parserHandler) ;
responseXmlReader.parse(new InputSource(new ByteArrayInputStream(responseBytes))) ;
final StringBuffer newResponseBuffer = newResponseWriter.getBuffer() ;
final String newResponseString = newResponseBuffer.toString();
final String responseString = (jbossClient ? newResponseString : new String(responseBytes));
if ((contentType != null) && !(SOAP_11_CONTENT_TYPE.equals(contentType) ||
SOAP_12_CONTENT_TYPE.equals(contentType)))
{
ProxyConversation.appendConversation(conversationIdentifier, escapeContents(responseString)) ;
}
else
{
ProxyConversation.appendConversation(conversationIdentifier, responseString) ;
}
final ServletOutputStream sos = httpServletResponse.getOutputStream() ;
sos.print(newResponseString) ;
sos.flush() ;
httpServletResponse.setContentLength(newResponseString.length()) ;
}
finally
{
is.close() ;
}
} else {
httpServletResponse.setContentLength(0) ;
}
} else {
httpServletResponse.setContentLength(0) ;
}
break ;
}
}
finally
{
destConnection.disconnect() ;
}
}
catch (Exception exception)
{
System.err.println("Proxy Listener Service: " + exception);
exception.printStackTrace() ;
}
catch (Error error)
{
System.err.println("Proxy Listener Service: " + error);
error.printStackTrace() ;
}
}
/**
* Trim quotes from the action.
* @param action The action.
* @return The trimmed action.
*/
private static String trimAction(final String action)
{
final int length = (action == null ? 0 : action.length()) ;
if ((length < 2) || (action.charAt(0) != '"') || (action.charAt(length-1) != '"'))
{
return action ;
}
return action.substring(1, length-1) ;
}
/**
* Get the content type part.
* @param fullContentType The full content type.
* @return The content type.
*/
private static String getContentType(final String fullContentType)
{
if (fullContentType == null)
{
return null ;
}
final int separatorIndex = fullContentType.indexOf(';') ;
return (separatorIndex == -1 ? fullContentType : fullContentType.substring(0, separatorIndex)) ;
}
/**
* Get the conversation identifier from the request.
* @return The conversation identifier.
*/
private static String getConversationIdentifier(final HttpServletRequest httpServletRequest)
{
final String pathInfo = httpServletRequest.getPathInfo() ;
final int separator = pathInfo.indexOf('/', 1) ;
return pathInfo.substring(1, separator) ;
}
/**
* Get the contents of the input stream
* @param is The input stream.
* @return The contents.
* @throws IOException for errors.
*/
private byte[] getContents(final InputStream is)
throws IOException
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
final byte[] buffer = new byte[1024] ;
int readCount ;
do
{
readCount = is.read(buffer, 0, buffer.length) ;
if (readCount > 0)
{
baos.write(buffer, 0, readCount) ;
}
}
while(readCount > 0) ;
return baos.toByteArray() ;
}
/**
* Check for the XML declaration and remove.
* This method is only used if we are intending to log the SOAP message so that it is easy to combine the XML without creating invalid documents.
* @param contents The current stream contents.
* @return The stream contents as a string.
*/
private static String checkForXMLDecl(final StringBuffer contents)
{
int count = 0 ;
try
{
while(Character.isWhitespace(contents.charAt(count))) count++ ;
if (contents.charAt(count) == '<')
{
if (contents.charAt(count+1) == '?')
{
count+=2 ;
while(contents.charAt(count++) != '>') ;
}
}
if (count > 0)
{
contents.delete(0, count) ;
}
}
catch (final StringIndexOutOfBoundsException sioobe) {}
return contents.toString() ;
}
/**
* Escape the contents of the string.
* @param contents The original contents.
* @return The escaped contents.
*/
private static String escapeContents(final String contents)
{
final int length = contents.length() ;
StringWriter escapedContents = null ;
for(int count = 0 ; count < length ; count++)
{
final char ch = contents.charAt(count) ;
if ((ch == '<') || (ch == '>') || (ch == '&') || (ch == '"'))
{
if (escapedContents == null)
{
escapedContents = new StringWriter(length) ;
if (count > 0)
{
escapedContents.write(contents, 0, count-1) ;
}
}
if (ch == '<')
{
escapedContents.write("<") ;
}
else if (ch == '>')
{
escapedContents.write(">") ;
}
else if (ch == '&')
{
escapedContents.write("&") ;
}
else if (ch == '"')
{
escapedContents.write(""") ;
}
}
else if (escapedContents != null)
{
escapedContents.write(ch) ;
}
}
return (escapedContents == null ? contents : escapedContents.toString()) ;
}
}