blob: 9761f56d3dd9c7a5848814858e90731dccc14c11 [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 org.apache.felix.http.base.internal.handler;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;
import org.apache.felix.http.base.internal.context.ExtServletContext;
/**
* The session wrapper keeps track of the internal session, manages their attributes
* separately and also handles session timeout.
*/
public class HttpSessionWrapper implements HttpSession
{
/** All special attributes are prefixed with this prefix. */
private static final String PREFIX = "org.apache.felix.http.session.context.";
/** For each internal session, the attributes are prefixed with this followed by the context id */
private static final String ATTR_PREFIX = PREFIX + "attr.";
/** The created time for the internal session (appended with context id) */
private static final String ATTR_CREATED = PREFIX + "created.";
/** The last accessed time for the internal session (appended with context id) */
private static final String ATTR_LAST_ACCESSED = PREFIX + "lastaccessed.";
/** The max inactive time (appended with context id) */
private static final String ATTR_MAX_INACTIVE = PREFIX + "maxinactive.";
/** The underlying container session. */
private final HttpSession delegate;
/** The corresponding servlet context. */
private final ExtServletContext context;
/** The id for this session. */
private final String sessionId;
/** The key prefix for attributes belonging to this session. */
private final String keyPrefix;
/** Flag to handle the validity of this session. */
private volatile boolean isInvalid = false;
/** The time this has been created. */
private final long created;
/** The time this has been last accessed. */
private final long lastAccessed;
/** The max timeout interval. */
private int maxTimeout;
/**
* Is this a new session?
*/
private final boolean isNew;
public static boolean hasSession(final long contextId, final HttpSession session)
{
final String sessionId = String.valueOf(contextId);
return session.getAttribute(ATTR_CREATED + sessionId) != null;
}
public static Set<Long> getExpiredSessionContextIds(final HttpSession session)
{
final long now = System.currentTimeMillis();
final Set<Long> ids = new HashSet<Long>();
final Enumeration<String> names = session.getAttributeNames();
while ( names.hasMoreElements() )
{
final String name = names.nextElement();
if ( name.startsWith(ATTR_LAST_ACCESSED) )
{
final String id = name.substring(ATTR_LAST_ACCESSED.length());
final long lastAccess = (Long)session.getAttribute(name);
final Integer maxTimeout = (Integer)session.getAttribute(ATTR_MAX_INACTIVE + id);
if ( maxTimeout > 0 && lastAccess + maxTimeout < now )
{
ids.add(Long.valueOf(id));
}
}
}
return ids;
}
public static Set<Long> getSessionContextIds(final HttpSession session)
{
final Set<Long> ids = new HashSet<Long>();
final Enumeration<String> names = session.getAttributeNames();
while ( names.hasMoreElements() )
{
final String name = names.nextElement();
if ( name.startsWith(ATTR_LAST_ACCESSED) )
{
final String id = name.substring(ATTR_LAST_ACCESSED.length());
ids.add(Long.valueOf(id));
}
}
return ids;
}
/**
* Creates a new {@link HttpSessionWrapper} instance.
*/
public HttpSessionWrapper(final long contextId,
final HttpSession session,
final ExtServletContext context,
final boolean terminate)
{
this.delegate = session;
this.context = context;
this.sessionId = String.valueOf(contextId);
this.keyPrefix = ATTR_PREFIX + this.sessionId + ".";
if ( this.keyPrefix != null )
{
final long now = System.currentTimeMillis();
if ( session.getAttribute(ATTR_CREATED + this.sessionId) == null )
{
this.created = now;
this.maxTimeout = session.getMaxInactiveInterval();
isNew = true;
session.setAttribute(ATTR_CREATED + this.sessionId, this.created);
session.setAttribute(ATTR_MAX_INACTIVE + this.sessionId, this.maxTimeout);
if ( context.getHttpSessionListener() != null )
{
context.getHttpSessionListener().sessionCreated(new HttpSessionEvent(this));
}
}
else
{
this.created = (Long)session.getAttribute(ATTR_CREATED + this.sessionId);
this.maxTimeout = (Integer)session.getAttribute(ATTR_MAX_INACTIVE + this.sessionId);
isNew = false;
}
this.lastAccessed = now;
if ( !terminate )
{
session.setAttribute(ATTR_LAST_ACCESSED + this.sessionId, this.lastAccessed);
}
}
else
{
this.isNew = session.isNew();
this.lastAccessed = session.getLastAccessedTime();
this.created = session.getCreationTime();
}
}
/**
* Helper method to get the real key within the real session.
*/
private String getKey(final String name)
{
return this.keyPrefix == null ? name : this.keyPrefix.concat(name);
}
/**
* Check whether this session is still valid.
* @throws IllegalStateException if session is not valid anymore
*/
private void checkInvalid()
{
if ( this.isInvalid )
{
throw new IllegalStateException("Session is invalid.");
}
}
@Override
public Object getAttribute(final String name)
{
this.checkInvalid();
Object result = this.delegate.getAttribute(this.getKey(name));
if ( result instanceof SessionBindingValueListenerWrapper )
{
result = ((SessionBindingValueListenerWrapper)result).getHttpSessionBindingListener();
}
return result;
}
@Override
public Enumeration<String> getAttributeNames()
{
this.checkInvalid();
final Enumeration<String> e = this.delegate.getAttributeNames();
return new Enumeration<String>() {
String next = peek();
private String peek()
{
while ( e.hasMoreElements() )
{
final String name = e.nextElement();
if ( keyPrefix == null && !name.startsWith(PREFIX) )
{
return name;
}
if ( name.startsWith(keyPrefix))
{
return name.substring(keyPrefix.length());
}
}
return null;
}
@Override
public boolean hasMoreElements() {
return next != null;
}
@Override
public String nextElement() {
if ( next == null )
{
throw new NoSuchElementException();
}
final String result = next;
next = this.peek();
return result;
}
};
}
@Override
public long getCreationTime()
{
this.checkInvalid();
return this.created;
}
@Override
public String getId()
{
this.checkInvalid();
return this.delegate.getId() + "-" + this.sessionId;
}
@Override
public long getLastAccessedTime()
{
this.checkInvalid();
return this.lastAccessed;
}
@Override
public int getMaxInactiveInterval()
{
// no validity check conforming to the javadocs
return this.maxTimeout;
}
@Override
public ServletContext getServletContext()
{
// no validity check conforming to the javadocs
return this.context;
}
@Override
public Object getValue(String name)
{
return this.getAttribute(name);
}
@Override
public String[] getValueNames()
{
final List<String> names = new ArrayList<String>();
final Enumeration<String> e = this.getAttributeNames();
while ( e.hasMoreElements() )
{
names.add(e.nextElement());
}
return names.toArray(new String[names.size()]);
}
@Override
public void invalidate()
{
this.checkInvalid();
if ( this.keyPrefix != null )
{
this.delegate.removeAttribute(ATTR_CREATED + this.sessionId);
this.delegate.removeAttribute(ATTR_LAST_ACCESSED + this.sessionId);
this.delegate.removeAttribute(ATTR_MAX_INACTIVE + this.sessionId);
final Enumeration<String> names = this.delegate.getAttributeNames();
while ( names.hasMoreElements() )
{
final String name = names.nextElement();
if ( name.startsWith(this.keyPrefix) ) {
this.removeAttribute(name.substring(this.keyPrefix.length()));
}
}
}
// if the session is empty we can invalidate
final Enumeration<String> names = this.delegate.getAttributeNames();
if ( !names.hasMoreElements() )
{
this.delegate.invalidate();
}
if ( context.getHttpSessionListener() != null )
{
context.getHttpSessionListener().sessionDestroyed(new HttpSessionEvent(this));
}
this.isInvalid = true;
}
@Override
public boolean isNew()
{
this.checkInvalid();
return this.isNew;
}
@Override
public void putValue(final String name, final Object value)
{
this.setAttribute(name, value);
}
@Override
public void removeAttribute(final String name)
{
this.checkInvalid();
final Object oldValue = this.getAttribute(name);
if ( oldValue != null )
{
this.delegate.removeAttribute(this.getKey(name));
if ( this.keyPrefix != null && oldValue instanceof HttpSessionBindingListener )
{
((HttpSessionBindingListener)oldValue).valueUnbound(new HttpSessionBindingEvent(this, name));
}
if ( this.context.getHttpSessionAttributeListener() != null )
{
this.context.getHttpSessionAttributeListener().attributeRemoved(new HttpSessionBindingEvent(this, name, oldValue));
}
}
}
@Override
public void removeValue(final String name)
{
this.removeAttribute(name);
}
@Override
public void setAttribute(final String name, final Object value)
{
this.checkInvalid();
if ( value == null )
{
this.removeAttribute(name);
return;
}
final Object oldValue = this.getAttribute(name);
// wrap http session binding listener to avoid container calling it!
if ( this.keyPrefix != null && value instanceof HttpSessionBindingListener )
{
this.delegate.setAttribute(this.getKey(name),
new SessionBindingValueListenerWrapper((HttpSessionBindingListener)value));
}
else
{
this.delegate.setAttribute(this.getKey(name), value);
}
if ( this.keyPrefix != null && value instanceof HttpSessionBindingListener )
{
((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this, name));
}
if ( this.context.getHttpSessionAttributeListener() != null )
{
if ( oldValue != null )
{
this.context.getHttpSessionAttributeListener().attributeReplaced(new HttpSessionBindingEvent(this, name, oldValue));
}
else
{
this.context.getHttpSessionAttributeListener().attributeAdded(new HttpSessionBindingEvent(this, name, value));
}
}
}
@Override
public void setMaxInactiveInterval(final int interval)
{
if ( this.delegate.getMaxInactiveInterval() < interval )
{
this.delegate.setMaxInactiveInterval(interval);
}
if ( this.keyPrefix != null )
{
this.maxTimeout = interval;
this.delegate.setAttribute(ATTR_MAX_INACTIVE + this.sessionId, interval);
}
}
@Override
@SuppressWarnings("deprecation")
public HttpSessionContext getSessionContext()
{
// no need to check validity conforming to the javadoc
return this.delegate.getSessionContext();
}
private static final class SessionBindingValueListenerWrapper implements Serializable
{
private static final long serialVersionUID = 4009563108883768425L;
private final HttpSessionBindingListener listener;
public SessionBindingValueListenerWrapper(final HttpSessionBindingListener listener)
{
this.listener = listener;
}
public HttpSessionBindingListener getHttpSessionBindingListener()
{
return listener;
}
}
}