/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* [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.endpoints;
import java.io.ByteArrayOutputStream;
import java.net.SocketException;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import flex.management.runtime.messaging.endpoints.EndpointControl;
import flex.messaging.FlexContext;
import flex.messaging.HttpFlexSession;
import flex.messaging.MessageClient;
import flex.messaging.client.FlexClient;
import flex.messaging.config.ConfigMap;
import flex.messaging.config.ConfigurationException;
import flex.messaging.config.ConfigurationConstants;
import flex.messaging.endpoints.amf.AMFFilter;
import flex.messaging.io.MessageIOConstants;
import flex.messaging.io.amf.ActionContext;
import flex.messaging.messages.CommandMessage;
import flex.messaging.messages.Message;
import flex.messaging.util.SettingsReplaceUtil;
/**
* Base for all of HTTP-based endpoints.
*/
public abstract class BaseHTTPEndpoint extends AbstractEndpoint
{
//--------------------------------------------------------------------------
//
// Private Static Constants
//
//--------------------------------------------------------------------------
private static final String ADD_NO_CACHE_HEADERS = "add-no-cache-headers";
private static final String REDIRECT_URL = "redirect-url";
private static final String INVALIDATE_SESSION_ON_DISCONNECT = "invalidate-session-on-disconnect";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructs an unmanaged <code>BaseHTTPEndpoint</code>.
*/
public BaseHTTPEndpoint()
{
this(false);
}
/**
* Constructs an <code>BaseHTTPEndpoint</code> with the indicated management.
*
* @param enableManagement <code>true</code> if the <code>BaseHTTPEndpoint</code>
* is manageable; otherwise <code>false</code>.
*/
public BaseHTTPEndpoint(boolean enableManagement)
{
super(enableManagement);
}
//--------------------------------------------------------------------------
//
// Initialize, validate, start, and stop methods.
//
//--------------------------------------------------------------------------
/**
* Initializes the <code>Endpoint</code> with the properties.
* If subclasses override, they must call <code>super.initialize()</code>.
*
* @param id Id of the <code>Endpoint</code>.
* @param properties Properties for the <code>Endpoint</code>.
*/
public void initialize(String id, ConfigMap properties)
{
super.initialize(id, properties);
if (properties == null || properties.size() == 0)
return;
// General HTTP props.
addNoCacheHeaders = properties.getPropertyAsBoolean(ADD_NO_CACHE_HEADERS, true);
redirectURL = properties.getPropertyAsString(REDIRECT_URL, null);
invalidateSessionOnDisconnect = properties.getPropertyAsBoolean(INVALIDATE_SESSION_ON_DISCONNECT, false);
loginAfterDisconnect = properties.getPropertyAsBoolean(ConfigurationConstants.LOGIN_AFTER_DISCONNECT_ELEMENT, false);
validateEndpointProtocol();
}
/**
* Starts the <code>Endpoint<code> by creating a filter chain and setting
* up serializers and deserializers.
*/
public void start()
{
if (isStarted())
return;
super.start();
filterChain = createFilterChain();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Controller used to manage this endpoint.
*/
protected EndpointControl controller;
/**
* AMF processing filter chain used by this endpoint.
*/
protected AMFFilter filterChain;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// addNoCacheHeaders
//----------------------------------
protected boolean addNoCacheHeaders = true;
/**
* Returns the <code>add-no-cache-headers</code> property.
*
* @return <code>true</code> if <code>add-no-cache-headers</code> is enabled;
* otherwise <code>false</code>.
*/
public boolean isAddNoCacheHeaders()
{
return addNoCacheHeaders;
}
/**
* Sets the <code>add-no-cache-headers</code> property.
*
* @param addNoCacheHeaders
*/
public void setAddNoCacheHeaders(boolean addNoCacheHeaders)
{
this.addNoCacheHeaders = addNoCacheHeaders;
}
//----------------------------------
// loginAfterDisconnect
//----------------------------------
/**
* @exclude
* This is a property used on the client.
*/
protected boolean loginAfterDisconnect;
//----------------------------------
// invalidateSessionOnDisconnect
//----------------------------------
protected boolean invalidateSessionOnDisconnect;
/**
* Indicates whether the server session will be invalidated
* when a client channel disconnects.
* Default is <code>false</code>.
*
* @return <code>true</code> if the server session will be invalidated
* when a client channel disconnects.
*/
public boolean isInvalidateSessionOnDisconnect()
{
return invalidateSessionOnDisconnect;
}
/**
* Set to <code>true</code> to invalidate the server session for a client
* that disconnects its channel.
* Default is <code>false</code>.
*
* @param value Set to <code>true</code> to invalidate the server session for a client
* that disconnects its channel.
*/
public void setInvalidateSessionOnDisconnect(boolean value)
{
invalidateSessionOnDisconnect = value;
}
//----------------------------------
// redirectURL
//----------------------------------
protected String redirectURL;
/**
* Returns the <code>redirect-url</code> property.
*
* @return The <code>redirect-url</code> property.
*/
public String getRedirectURL()
{
return redirectURL;
}
/**
* Sets the <code>redirect-url</code> property.
*
* @param redirectURL
*/
public void setRedirectURL(String redirectURL)
{
this.redirectURL = redirectURL;
}
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
/**
* Handle AMF/AMFX encoded messages sent over HTTP.
*
* @param req The original servlet request.
* @param res The active servlet response.
*/
public void service(HttpServletRequest req, HttpServletResponse res)
{
super.service(req, res);
try
{
// Setup serialization and type marshalling contexts
setThreadLocals();
// Create a context for this request
ActionContext context = new ActionContext();
// Pass endpoint's mpi settings to the context so that it knows what level of
// performance metrics should be gathered during serialization/deserialization
context.setRecordMessageSizes(isRecordMessageSizes());
context.setRecordMessageTimes(isRecordMessageTimes());
// Send invocation through filter chain, which ends at the MessageBroker
filterChain.invoke(context);
// After serialization completes, increment endpoint byte counters,
// if the endpoint is managed
if (isManaged())
{
controller.addToBytesDeserialized(context.getDeserializedBytes());
controller.addToBytesSerialized(context.getSerializedBytes());
}
if (context.getStatus() != MessageIOConstants.STATUS_NOTAMF)
{
if (addNoCacheHeaders)
addNoCacheHeaders(req, res);
ByteArrayOutputStream outBuffer = context.getResponseOutput();
res.setContentType(getResponseContentType());
res.setContentLength(outBuffer.size());
outBuffer.writeTo(res.getOutputStream());
res.flushBuffer();
}
else
{
// Not an AMF request, probably viewed in a browser
if (redirectURL != null)
{
try
{
//Check for redirect URL context-root token
redirectURL = SettingsReplaceUtil.replaceContextPath(redirectURL, req.getContextPath());
res.sendRedirect(redirectURL);
}
catch (IllegalStateException alreadyFlushed)
{
}
}
}
}
catch (SocketException se)
{
// This happens when client closes the connection, log it at info level
log.info(se.getMessage());
}
catch (Throwable t)
{
log.error(t.getMessage(), t);
}
finally
{
clearThreadLocals();
}
}
/**
* @exclude
* Returns a <code>ConfigMap</code> of endpoint properties that the client
* needs. This includes properties from <code>super.describeEndpoint</code>
* and additional <code>BaseHTTPEndpoint</code> specific properties under
* "properties" key.
*/
public ConfigMap describeEndpoint()
{
ConfigMap endpointConfig = super.describeEndpoint();
boolean createdProperties = false;
ConfigMap properties = endpointConfig.getPropertyAsMap("properties", null);
if (properties == null)
{
properties = new ConfigMap();
createdProperties = true;
}
if (loginAfterDisconnect)
{
ConfigMap loginAfterDisconnect = new ConfigMap();
// Adding as a value rather than attribute to the parent
loginAfterDisconnect.addProperty("", "true");
properties.addProperty(ConfigurationConstants.LOGIN_AFTER_DISCONNECT_ELEMENT, loginAfterDisconnect);
}
if (createdProperties && properties.size() > 0)
endpointConfig.addProperty("properties", properties);
return endpointConfig;
}
//--------------------------------------------------------------------------
//
// Protected Methods
//
//--------------------------------------------------------------------------
/**
* Create the gateway filters that transform action requests
* and responses.
*/
protected abstract AMFFilter createFilterChain();
/**
* Returns the content type used by the connection handler to set on the
* HTTP response. Subclasses should either return MessageIOConstants.AMF_CONTENT_TYPE
* or MessageIOConstants.XML_CONTENT_TYPE.
*/
protected abstract String getResponseContentType();
/**
* @see flex.messaging.endpoints.AbstractEndpoint#handleChannelDisconnect(CommandMessage)
*/
protected Message handleChannelDisconnect(CommandMessage disconnectCommand)
{
HttpFlexSession session = (HttpFlexSession)FlexContext.getFlexSession();
FlexClient flexClient = FlexContext.getFlexClient();
// Shut down any subscriptions established over this channel/endpoint
// for this specific FlexClient.
if (flexClient.isValid())
{
String endpointId = getId();
List messageClients = flexClient.getMessageClients();
for (Iterator iter = messageClients.iterator(); iter.hasNext();)
{
MessageClient messageClient = (MessageClient)iter.next();
if (messageClient.getEndpointId().equals(endpointId))
{
messageClient.setClientChannelDisconnected(true);
messageClient.invalidate();
}
}
}
// And optionally invalidate the session.
if (session.isValid() && isInvalidateSessionOnDisconnect())
session.invalidate(false /* don't recreate */);
return super.handleChannelDisconnect(disconnectCommand);
}
protected void validateEndpointProtocol()
{
if (isSecure() && !url.startsWith("https:"))
{
ConfigurationException ce = new ConfigurationException();
ce.setMessage(11100, new Object[] {url, "https"});
throw ce;
}
}
}