blob: 3bf626a13b6f00529d957bd585b53879e37bfaf5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 flex.messaging;
import flex.messaging.log.Log;
import flex.messaging.log.LogCategories;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* FlexSession implementation for use with HTTP-based channels.
*/
public class HttpFlexSession extends FlexSession
implements HttpSessionBindingListener, HttpSessionListener, HttpSessionAttributeListener, Serializable
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
*
* Not for public use. This constructor is used to create an instance of this class that
* will operate as a javax.servlet.http.HttpSessionListener registered in web.xml.
*/
public HttpFlexSession()
{}
/**
*
* Not for public use. Constructs new instances that effectively wrap pre-existing JEE HttpSession instances.
*
* @param provider HttpFlexSessionProvider object
*/
public HttpFlexSession(HttpFlexSessionProvider provider)
{
super(provider);
}
//--------------------------------------------------------------------------
//
// Constants
//
//--------------------------------------------------------------------------
/**
* Serializable version uid.
*/
private static final long serialVersionUID = -1260409488935306147L;
/**
* Attribute name that HttpFlexSession is stored under in the HttpSession.
*/
/* package-private */ static final String SESSION_ATTRIBUTE = "__flexSession";
/**
* This attribute is set on the request associated with a Flex Session when
* a logout command is being processed. The reason that this is necessary is
* that a single HttpServletRequest may contain more than one Flex command/message.
* In this case, every message following a "logout" command should behave as if the
* user has logged out. However, since in getUserPrincipal, we check the request
* object if the Session has no principal, if the current request object
* has a principal object associated with it (as it does on Tomcat/JBoss),
* messages following a "logout" will use this principal. We thus need to
* invalidate the user principal in the request on logout. Since the field
* is read-only we do so using this attribute.
*/
private static final String INVALIDATED_REQUEST = "__flexInvalidatedRequest";
public static final String SESSION_MAP = "LCDS_HTTP_TO_FLEX_SESSION_MAP";
/**
* Internal flag indicating whether we are a registered listener in web.xml.
*/
/* package-private */ static volatile boolean isHttpSessionListener;
/**
* Flag to indicate whether we've logged a warning if we weren't registered in web.xml and
* can't redispatch attribute and binding events to Flex listeners.
*/
/* package-private */ static volatile boolean warnedNoEventRedispatch;
/**
* The log category to send the warning for no event redispatch to.
*/
/* package-private */ static String WARN_LOG_CATEGORY = LogCategories.CONFIGURATION;
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Reference to the HttpSession allows us to invalidate it and use it for attribute management.
*/
/* package-private */ HttpSession httpSession;
/**
*
* Static lock for creating httpSessionToFlexSession map
*/
public static final Object mapLock = new Object();
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
/**
* HttpSessionAttributeListener callback; processes the addition of an attribute to an HttpSession.
*
* NOTE: Callback is not made against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionBindingEvent
*/
public void attributeAdded(HttpSessionBindingEvent event)
{
if (!event.getName().equals(SESSION_ATTRIBUTE))
{
// Accessing flexSession via map because it may have already been unbound from httpSession.
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession());
HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.get(event.getSession().getId());
if (flexSession != null)
{
String name = event.getName();
Object value = event.getValue();
flexSession.notifyAttributeBound(name, value);
flexSession.notifyAttributeAdded(name, value);
}
}
}
/**
* HttpSessionAttributeListener callback; processes the removal of an attribute from an HttpSession.
*
* NOTE: Callback is not made against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionBindingEvent
*/
public void attributeRemoved(HttpSessionBindingEvent event)
{
if (!event.getName().equals(SESSION_ATTRIBUTE))
{
// Accessing flexSession via map because it may have already been unbound from httpSession.
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession());
HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.get(event.getSession().getId());
if (flexSession != null)
{
String name = event.getName();
Object value = event.getValue();
flexSession.notifyAttributeUnbound(name, value);
flexSession.notifyAttributeRemoved(name, value);
}
}
}
/**
* HttpSessionAttributeListener callback; processes the replacement of an attribute in an HttpSession.
*
* NOTE: Callback is not made against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionBindingEvent
*/
public void attributeReplaced(HttpSessionBindingEvent event)
{
if (!event.getName().equals(SESSION_ATTRIBUTE))
{
// Accessing flexSession via map because it may have already been unbound from httpSession.
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession());
HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.get(event.getSession().getId());
if (flexSession != null)
{
String name = event.getName();
Object value = event.getValue();
Object newValue = flexSession.getAttribute(name);
flexSession.notifyAttributeUnbound(name, value);
flexSession.notifyAttributeReplaced(name, value);
flexSession.notifyAttributeBound(name, newValue);
}
}
}
/**
* Creates or retrieves a FlexSession for the current Http request.
* The HttpFlexSession wraps the underlying J2EE HttpSession.
* Not intended for public use.
*
* @param req The Http request.
*
* @return The HttpFlexSession.
*
* @deprecated This method has been deprecated in favor of session providers registered with a <tt>MessageBroker</tt>.
* @see flex.messaging.FlexSessionManager
* @see flex.messaging.HttpFlexSessionProvider
*/
public static HttpFlexSession getFlexSession(HttpServletRequest req)
{
HttpFlexSession flexSession;
HttpSession httpSession = req.getSession(true);
if (!isHttpSessionListener && !warnedNoEventRedispatch)
{
warnedNoEventRedispatch = true;
if (Log.isWarn())
Log.getLogger(WARN_LOG_CATEGORY).warn("HttpFlexSession has not been registered as a listener in web.xml for this application so no events will be dispatched to FlexSessionAttributeListeners or FlexSessionBindingListeners. To correct this, register flex.messaging.HttpFlexSession as a listener in web.xml.");
}
boolean isNew = false;
synchronized (httpSession)
{
flexSession = (HttpFlexSession)httpSession.getAttribute(HttpFlexSession.SESSION_ATTRIBUTE);
if (flexSession == null)
{
flexSession = new HttpFlexSession();
// Correlate this FlexSession to the HttpSession before triggering any listeners.
FlexContext.setThreadLocalSession(flexSession);
httpSession.setAttribute(SESSION_ATTRIBUTE, flexSession);
flexSession.setHttpSession(httpSession);
isNew = true;
}
else
{
FlexContext.setThreadLocalSession(flexSession);
if (flexSession.httpSession == null)
{
// httpSession is null if the instance is new or is from
// serialization.
flexSession.setHttpSession(httpSession);
isNew = true;
}
}
}
if (isNew)
{
flexSession.notifyCreated();
if (Log.isDebug())
Log.getLogger(FLEX_SESSION_LOG_CATEGORY).debug("FlexSession created with id '" + flexSession.getId() + "' for an Http-based client connection.");
}
return flexSession;
}
/**
* Returns the user principal associated with the session. This will
* be null if the user has not authenticated.
*
* @return The Principal associated with the session.
*/
public Principal getUserPrincipal()
{
Principal p = super.getUserPrincipal();
if (p == null)
{
HttpServletRequest req = FlexContext.getHttpRequest();
if (req != null && req.getAttribute(INVALIDATED_REQUEST) == null)
p = req.getUserPrincipal();
}
return p;
}
/**
* Invalidates the session.
*/
public void invalidate()
{
// If the HttpFlexSession is the current active FlexSession for the thread
// we'll invalidate it but we need to recreate a new HttpFlexSession because
// the client's HttpSession is still active.
boolean recreate = FlexContext.getFlexSession() == this;
invalidate(recreate);
}
/**
*
* Used by Http endpoints when they receive notification from a client that it has
* disconnected its channel.
* Supports invalidating the HttpFlexSession and underlying JEE HttpSession without
* triggering session recreation.
*
* @param recreate true if the http session should be recreated.
*/
public void invalidate(boolean recreate)
{
synchronized (httpSession)
{
try
{
// Invalidating the HttpSession will trigger invalidation of the HttpFlexSession
// either via the sessionDestroyed() event if registration as an HttpSession listener worked
// or via the valueUnbound() event if it didn't.
httpSession.invalidate();
}
catch (IllegalStateException e)
{
// Make sure any related mapping is removed.
try
{
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(httpSession);
httpSessionToFlexSessionMap.remove(httpSession.getId());
}
catch (Exception ignore)
{
// NOWARN
}
// And invalidate this FlexSession.
super.invalidate();
}
}
if (recreate)
{
HttpServletRequest req = FlexContext.getHttpRequest();
if (req != null)
{
// Set an attribute on the request denoting that the userPrincipal in the request
// is now invalid.
req.setAttribute(INVALIDATED_REQUEST, "true");
AbstractFlexSessionProvider sessionProvider = getFlexSessionProvider();
// BLZ-531: When using spring integration getting a null pointer exception when calling invalidate
// on a FlexSession twice
// If originally the HttpFlexSession was created using the deprecated HttpFlexSession.getFlexSession(request) API,
// it does not have an associated AbstractFlexSessionProvider. Invoking invalidate(true) on such a session
// results in the "recreated" FlexSession being NULL. To prevent this from happening, in case session provider
// is NULL, we create the session using the deprecated HttpFlexSession.getFlexSession(request) API.
FlexSession session = sessionProvider == null ?
getFlexSession(req) : ((HttpFlexSessionProvider)sessionProvider).getOrCreateSession(req);
FlexContext.setThreadLocalObjects(FlexContext.getFlexClient(),
session, FlexContext.getMessageBroker(), req,
FlexContext.getHttpResponse(), FlexContext.getServletConfig());
}
// else, the session was invalidated outside of a request being processed.
}
}
/**
* Returns the attribute bound to the specified name in the session, or null
* if no attribute is bound under the name.
*
* @param name The name the target attribute is bound to.
* @return The attribute bound to the specified name.
*/
public Object getAttribute(String name)
{
return httpSession.getAttribute(name);
}
/**
* Returns the names of all attributes bound to the session.
*
* @return The names of all attributes bound to the session.
*/
public Enumeration getAttributeNames()
{
return httpSession.getAttributeNames();
}
/**
* Returns the Id for the session.
*
* @return The Id for the session.
*/
public String getId()
{
return httpSession.getId();
}
/**
*
* FlexClient invokes this to determine whether the session can be used to push messages
* to the client.
*
* @return true if the FlexSession supports direct push; otherwise false (polling is assumed).
*/
public boolean isPushSupported()
{
return false;
}
/**
* Removes the attribute bound to the specified name in the session.
*
* @param name The name of the attribute to remove.
*/
public void removeAttribute(String name)
{
httpSession.removeAttribute(name);
}
/**
* Implements HttpSessionListener.
* HttpSession created events are handled by setting an internal flag indicating that registration
* as an HttpSession listener was successful and we will be notified of session attribute changes and
* session destruction.
* NOTE: This method is not invoked against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionEvent
*/
public void sessionCreated(HttpSessionEvent event)
{
isHttpSessionListener = true;
}
/**
* Implements HttpSessionListener.
* When an HttpSession is destroyed, the associated HttpFlexSession is also destroyed.
* NOTE: This method is not invoked against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionEvent
*/
public void sessionDestroyed(HttpSessionEvent event)
{
HttpSession session = event.getSession();
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(session);
HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.remove(session.getId());
if (flexSession != null)
{
// invalidate the flex session
flexSession.superInvalidate();
// Send notifications to attribute listeners if needed.
// This may send extra notifications if attributeRemoved is called first by the server,
// but Java servlet 2.4 says session destroy is first, then attributes.
// Guard against pre-2.4 containers that dispatch events in an incorrect order,
// meaning skip attribute processing here if the underlying session state is no longer valid.
try
{
for (Enumeration e = session.getAttributeNames(); e.hasMoreElements(); )
{
String name = (String) e.nextElement();
if (name.equals(SESSION_ATTRIBUTE))
continue;
Object value = session.getAttribute(name);
if (value != null)
{
flexSession.notifyAttributeUnbound(name, value);
flexSession.notifyAttributeRemoved(name, value);
}
}
}
catch (IllegalStateException ignore)
{
// NOWARN
// Old servlet container that dispatches events out of order.
}
}
}
/**
* Binds an attribute to the session under the specified name.
*
* @param name The name to bind the attribute under.
*
* @param value The attribute value.
*/
public void setAttribute(String name, Object value)
{
httpSession.setAttribute(name, value);
}
/**
* Implements HttpSessionBindingListener.
* This is a no-op.
* NOTE: This method is not invoked against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionBindingEvent
*/
public void valueBound(HttpSessionBindingEvent event)
{
// No-op.
}
/**
* Implements HttpSessionBindingListener.
* This callback will destroy the HttpFlexSession upon being unbound, only in the
* case where we haven't been registered as an HttpSessionListener in web.xml and
* can't shut down based on the HttpSession being invalidated.
* NOTE: This method is not invoked against an HttpFlexSession associated with a request
* handling thread.
* @param event the HttpSessionBindingEvent
*/
public void valueUnbound(HttpSessionBindingEvent event)
{
if (!isHttpSessionListener)
{
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(event.getSession());
HttpFlexSession flexSession = (HttpFlexSession)httpSessionToFlexSessionMap.remove(event.getSession().getId());
if (flexSession != null)
flexSession.superInvalidate();
}
}
//--------------------------------------------------------------------------
//
// Protected Methods
//
//--------------------------------------------------------------------------
/**
* We don't need to do anything here other than log out some info about the session that's shutting down.
*/
protected void internalInvalidate()
{
if (Log.isDebug())
Log.getLogger(FLEX_SESSION_LOG_CATEGORY).debug("FlexSession with id '" + getId() + "' for an Http-based client connection has been invalidated.");
}
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
/**
* Associates a HttpSession with the FlexSession.
*
* @param httpSession The HttpSession to associate with the FlexSession.
*/
/* package-private */ void setHttpSession(HttpSession httpSession)
{
synchronized (lock)
{
this.httpSession = httpSession;
// Update lookup table for event redispatch.
Map httpSessionToFlexSessionMap = getHttpSessionToFlexSessionMap(httpSession);
httpSessionToFlexSessionMap.put(httpSession.getId(), this);
}
}
/**
*
* Invoked by HttpSessionListener or binding listener on HttpSession invalidation to invalidate the wrapping
* FlexSession.
*/
private void superInvalidate()
{
super.invalidate();
}
/**
* Implements Serializable; only the Principal needs to be serialized as all
* attribute storage is delegated to the associated HttpSession.
*
* @param stream The stream to read instance state from.
*/
private void writeObject(ObjectOutputStream stream)
{
try
{
Principal principal = super.getUserPrincipal();
if (principal != null && principal instanceof Serializable)
stream.writeObject(principal);
}
catch (IOException e)
{
// Principal was Serializable and non-null; if this happens there's nothing we can do.
// The user will need to reauthenticate if necessary.
}
catch (LocalizedException ignore)
{
// This catch block added for bug 194144.
// On BEA WebLogic, writeObject() is sometimes invoked on invalidated session instances
// and in this case the checkValid() invocation in super.getUserPrincipal() throws.
// Ignore this exception.
}
}
/**
* Implements Serializable; only the Principal needs to be serialized as all
* attribute storage is delegated to the associated HttpSession.
*
* @param stream The stream to write instance state to.
*/
private void readObject(ObjectInputStream stream)
{
try
{
setUserPrincipal((Principal)stream.readObject());
}
catch (Exception e)
{
// Principal was not serialized or failed to serialize; ignore.
// The user will need to reauthenticate if necessary.
}
}
/**
* Map of HttpSession Ids to FlexSessions. We need this when registered as a listener
* in web.xml in order to trigger the destruction of a FlexSession when its associated HttpSession
* is invalidated/destroyed. The Servlet spec prior to version 2.4 defined the session destruction event
* to be dispatched after attributes are unbound from the session so when we receive notification that
* an HttpSession is destroyed there's no way to get to the associated FlexSession attribute because it
* has already been unbound... Additionally, we need it to handle attribute removal events that happen
* during HttpSession destruction because the FlexSession can be unbound from the session before the
* other attributes we receive notification for.
*
* Because of this, it's simplest to just maintain this lookup table and use it for all HttpSession
* related event handling.
*
* The table is maintained on the servlet context instead of statically in order to prevent collisions
* across web-apps.
*/
private Map getHttpSessionToFlexSessionMap(HttpSession session)
{
try
{
ServletContext context = session.getServletContext();
Map map = (Map)context.getAttribute(SESSION_MAP);
if(map==null){
// map should never be null here as it is created during MessageBrokerServlet start-up
if (Log.isError())
Log.getLogger(FLEX_SESSION_LOG_CATEGORY).error("HttpSession to FlexSession map not created in message broker for "
+ session.getId());
MessageException me = new MessageException();
me.setMessage(10032, new Object[] {session.getId()});
throw me;
}
return map;
}
catch(Exception e)
{
if (Log.isDebug())
Log.getLogger(FLEX_SESSION_LOG_CATEGORY).debug("Unable to get HttpSession to FlexSession map for "
+ session.getId() + " " + e.toString());
return new ConcurrentHashMap();
}
}
}