/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2002 - 2007 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated
* and its suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package flex.messaging.services;
import flex.management.runtime.messaging.services.HTTPProxyServiceControl;
import flex.management.runtime.messaging.services.http.HTTPProxyDestinationControl;
import flex.messaging.Destination;
import flex.messaging.FlexRemoteCredentials;
import flex.messaging.MessageBroker;
import flex.messaging.MessageException;
import flex.messaging.FlexContext;
import flex.messaging.messages.HTTPMessage;
import flex.messaging.messages.Message;
import flex.messaging.messages.SOAPMessage;
import flex.messaging.services.http.HTTPProxyDestination;
import flex.messaging.services.http.proxy.ProxyException;
import flex.messaging.util.SettingsReplaceUtil;
import flex.messaging.util.StringUtils;
import flex.messaging.log.LogCategories;
import flex.messaging.log.Log;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
/**
* The HttpProxyService replaces the Flex 1.5 Proxy. It decouples
* the details of how the client contacts the message broker
* by accepting an HTTPMessage type which can be sent over
* any channel.
*
* @author Cathy Murphy
* @author Glen Daniels
* @author Brian Deitte
* @author Sean Neville
* @author Peter Farland
*
* @see flex.messaging.messages.HTTPMessage
*/
public class HTTPProxyService extends AbstractService
{
/** Log category for <code>HTTPProxyService</code>. */
public static final String LOG_CATEGORY = LogCategories.SERVICE_HTTP;
// Errors
private static final int DOT_DOT_NOT_ALLOWED = 10700;
private static final int MULTIPLE_DOMAIN_PORT = 10701;
private static final int DYNAMIC_NOT_CONFIGURED = 10702;
private HTTPProxyServiceControl controller;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructs an unmanaged <code>HTTPProxyService</code>.
*/
public HTTPProxyService()
{
this(false);
}
/**
* Constructs a <code>HTTPProxyService</code> with the indicated management.
*
* @param enableManagement <code>true</code> if the <code>HTTPProxyService</code>
* is manageable; otherwise <code>false</code>.
*/
public HTTPProxyService(boolean enableManagement)
{
super(enableManagement);
}
//--------------------------------------------------------------------------
//
// Public Getters and Setters for HTTPProxyService properties
//
//--------------------------------------------------------------------------
/**
* Creates a <code>HTTPProxyDestination</code> instance, sets its id,
* sets it manageable if the <code>AbstractService</code> that created it is
* manageable, and sets its <code>Service</code> to the <code>AbstractService</code>
* that created it.
*
* @param id The id of the <code>HTTPProxyDestination</code>.
* @return The <code>Destination</code> instanced created.
*/
@Override
public Destination createDestination(String id)
{
HTTPProxyDestination destination = new HTTPProxyDestination();
destination.setId(id);
destination.setManaged(isManaged());
destination.setService(this);
return destination;
}
/**
* Casts the <code>Destination</code> into <code>HTTPProxyDestination</code>
* and calls super.addDestination.
*
* @param destination The <code>Destination</code> instance to be added.
*/
@Override
public void addDestination(Destination destination)
{
HTTPProxyDestination proxyDestination = (HTTPProxyDestination)destination;
super.addDestination(proxyDestination);
}
//--------------------------------------------------------------------------
//
// Other Public APIs
//
//--------------------------------------------------------------------------
/**
* Processes messages of type <code>HTTPMessage</code> by invoking the
* requested destination's adapter.
*
* @param msg The message sent by the MessageBroker.
* @return The result of the service.
*/
@Override
public Object serviceMessage(Message msg)
{
if (!(msg instanceof HTTPMessage))
{
// The 'HTTPProxy' Service can only process messages of type 'HTTPMessage'.
ServiceException e = new ServiceException();
e.setMessage(UNKNOWN_MESSAGE_TYPE, new Object[]{"HTTPProxy", "HTTPMessage"});
throw e;
}
HTTPMessage message = (HTTPMessage)msg;
String destination = message.getDestination();
HTTPProxyDestination dest = (HTTPProxyDestination)destinations.get(destination);
//use the remote settings if the message didn't specify them
FlexRemoteCredentials remoteCredentials =
FlexContext.getFlexSession().getRemoteCredentials(getId(), destination);
if (remoteCredentials != null)
{
message.setRemoteUsername(remoteCredentials.getUsername());
message.setRemotePassword((String)remoteCredentials.getCredentials());
}
else if (dest.getRemoteUsername() != null && dest.getRemotePassword() != null)
{
message.setRemoteUsername(dest.getRemoteUsername());
message.setRemotePassword(dest.getRemotePassword());
}
ServiceAdapter adapter = dest.getAdapter();
Object result;
if (message instanceof SOAPMessage)
{
result = invokeSoap(adapter, (SOAPMessage)message, dest);
}
else
{
result = invokeHttp(adapter, message, dest);
}
if (Log.isDebug())
{
String debugResult =
StringUtils.prettifyString(String.valueOf(result));
Log.getLogger(getLogCategory()).debug
("HTTP request: " +
message + StringUtils.NEWLINE +
" response: " + StringUtils.NEWLINE +
debugResult + StringUtils.NEWLINE);
}
return result;
}
//--------------------------------------------------------------------------
//
// Protected/private APIs
//
//--------------------------------------------------------------------------
protected Object invokeSoap(ServiceAdapter adapter, SOAPMessage message, HTTPProxyDestination destination)
{
if (isManaged())
{
HTTPProxyDestinationControl destinationControl = (HTTPProxyDestinationControl)destination.getControl();
if (destinationControl != null)
destinationControl.incrementInvokeSOAPCount();
}
String dynamicUrl = message.getUrl();
String contextPath = null;
String serverName = null;
String serverPort = null;
String protocol = null;
HttpServletRequest req = FlexContext.getHttpRequest();
if (req != null)
{
contextPath = req.getContextPath();
protocol = req.getScheme();
serverName = req.getServerName();
int port = req.getServerPort();
if (port != 0)
{
serverPort = Integer.valueOf(req.getServerPort()).toString();
}
}
if (dynamicUrl != null && dynamicUrl.length() > 0)
{
checkUrl(dynamicUrl, contextPath, destination, serverName, serverPort, protocol, message.getRemoteUsername() != null);
}
else
{
//TODO: QUESTION: Pete Support default soap endpoints?
//String url = settings.getParsedDefaultUrl(contextPath);
//message.setUrl(url);
// FIXME: Need a better error here!
throw new MessageException("A SOAP endpoint was not provided.");
}
return adapter.invoke(message);
}
protected void checkUrl(String url, String contextPath, HTTPProxyDestination destination, String serverName,
String serverPort, String serverProtocol, boolean authSupplied)
{
String originalUrl = url;
String defaultUrl = destination.getParsedDefaultUrl(contextPath, serverName, serverPort, serverProtocol);
List dynamicUrls = destination.getParsedDynamicUrls(contextPath);
//If we find ".." in a URL provided by the client, someone's likely
//trying to trick us. Ask them to do it another way if so.
int i = url.indexOf("/..");
while (i != -1)
{
if (i == (url.length() - 3) || url.charAt(i + 3) == '/')
{
throw new ProxyException(DOT_DOT_NOT_ALLOWED);
}
i = url.indexOf("/..", i + 1);
}
//Next, check if the URL is exactly the default URL
url = url.toLowerCase();
// In IPv6, update to long form, if required.
url = SettingsReplaceUtil.updateIPv6(url);
if (defaultUrl != null && defaultUrl.equalsIgnoreCase(url))
return;
char[] urlChars = url.toCharArray();
// Next, check that the URL matches a dynamic URL pattern
for (i = 0; i < dynamicUrls.size(); i++)
{
char[] pattern = (char[])dynamicUrls.get(i);
boolean matches = StringUtils.findMatchWithWildcard(urlChars, pattern);
if (matches)
{
if (!authSupplied || destination.allowsDynamicAuthentication())
return;
throw new ProxyException(MULTIPLE_DOMAIN_PORT);
}
}
ProxyException exception = new ProxyException();
exception.setMessage
(DYNAMIC_NOT_CONFIGURED, new Object[] {originalUrl, destination.getId()});
throw exception;
}
/**
* Returns the log category of the <code>HTTPProxyService</code>.
*
* @return The log category of the component.
*/
@Override
protected String getLogCategory()
{
return LOG_CATEGORY;
}
protected Object invokeHttp(ServiceAdapter adapter, HTTPMessage message, HTTPProxyDestination destination)
{
if (isManaged())
{
HTTPProxyDestinationControl destinationControl = (HTTPProxyDestinationControl)destination.getControl();
if (destinationControl != null)
destinationControl.incrementInvokeHTTPCount();
}
String dynamicUrl = message.getUrl();
String contextPath = null;
String serverName = null;
String serverPort = null;
String protocol = null;
HttpServletRequest req = FlexContext.getHttpRequest();
if (req != null)
{
contextPath = req.getContextPath();
protocol = req.getScheme();
serverName = req.getServerName();
int port = req.getServerPort();
if (port != 0)
{
serverPort = Integer.toString(req.getServerPort());
}
}
if (dynamicUrl != null && !"".equals(dynamicUrl))
{
checkUrl(dynamicUrl, contextPath, destination, serverName, serverPort, protocol, message.getRemoteUsername() != null);
}
else
{
String url = destination.getParsedDefaultUrl(contextPath, serverName, serverPort, protocol);
message.setUrl(url);
}
return adapter.invoke(message);
}
/**
* This method is invoked to allow the <code>HTTPProxyService</code> to instantiate and register its
* MBean control.
*
* @param broker The <code>MessageBroker</code> to pass to the <code>HTTPProxyServiceControl</code> constructor.
*/
@Override
protected void setupServiceControl(MessageBroker broker)
{
controller = new HTTPProxyServiceControl(this, broker.getControl());
controller.register();
setControl(controller);
}
}