package hirondelle.fish.test.doubles;
import java.io.FileNotFoundException;
import java.util.*;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionBindingListener;
import hirondelle.web4j.util.Util;
import java.util.logging.*;
/**
Fake implementation of {@link HttpSession}, used
only for testing outside of the regular runtime environment.
*/
public class FakeSession implements HttpSession {
/**
Each incoming request calls this method in order to join an existing session, or create a new one.
@param aRequestedSessionId identifies the session that the request wishes to join.
@param aCreateNew if the request fails to join an existing session and <tt>aCreateNew</tt> is true, then
a new session is created. If <tt>aCreateNew</tt> is false, then no such new session is created.
@return the session which the request has joined, or <tt>null</tt> if no session was joined.
*/
public static synchronized HttpSession joinOrCreate(String aRequestedSessionId, boolean aCreateNew){
HttpSession result = tryToJoinExistingSession(aRequestedSessionId);
if( result == null && aCreateNew ) {
result = createNewSession();
}
if( result != null ) {
fLogger.fine("Join/Create Session. Id: " + result.getId());
}
else {
fLogger.fine("No session was joined.");
}
return result;
}
/** Returns the time this session object was created. */
public long getCreationTime() {
vomitIfInvalidated();
return fCreationTime;
}
/** Return the session id. */
public String getId() {
return fSessionId;
}
public long getLastAccessedTime() {
vomitIfInvalidated();
return fLastAccessTime;
}
/** Returns a fake <tt>ServletContext</tt>. */
public ServletContext getServletContext() {
return fServletContext;
}
public void setMaxInactiveInterval(int aInterval) {
fMaxInactiveInterval = aInterval;
}
/** Default value 30 minutes. */
public int getMaxInactiveInterval() {
return fMaxInactiveInterval;
}
public Enumeration getAttributeNames() {
vomitIfInvalidated();
Hashtable<String, Object> hashTable = new Hashtable<String, Object>(fAttributes);
return hashTable.keys();
}
public Object getAttribute(String aName) {
vomitIfInvalidated();
return fAttributes.get(aName);
}
public void setAttribute(String aName, Object aObject) {
vomitIfInvalidated();
Object existingObject = fAttributes.put(aName, aObject);
if( aObject instanceof HttpSessionBindingListener) {
HttpSessionBindingListener listener = (HttpSessionBindingListener)aObject;
Object value = (existingObject != null ? existingObject : aObject);
listener.valueBound(new HttpSessionBindingEvent(this, aName, value));
}
}
public void removeAttribute(String aName) {
vomitIfInvalidated();
Object existingObject = fAttributes.remove(aName);
if( existingObject instanceof HttpSessionBindingListener) {
HttpSessionBindingListener listener = (HttpSessionBindingListener)existingObject;
listener.valueUnbound(new HttpSessionBindingEvent(this, aName, existingObject));
}
}
public void invalidate() {
vomitIfInvalidated();
List<String> attrs = Collections.list(getAttributeNames());
for(String name: attrs){
removeAttribute(name);
}
fAttributes.clear();
fHasBeenInvalidated = true;
//some might remove the session from fSession.
//for testing, that's likely not very important.
}
/** Returns <tt>true</tt> only if no request has yet joined this session. */
public boolean isNew() {
vomitIfInvalidated();
return fIsPropsective;
}
/** Does nothing - deprecated. */
public void putValue(String aName, Object aValue) { }
/** Does nothing - deprecated. */
public void removeValue(String aName) { }
/** Returns <tt>null</tt> - deprecated. */
public Object getValue(String aName) { return null; }
/** Returns <tt>null</tt> - deprecated. */
public HttpSessionContext getSessionContext() { return null; }
/** Returns <tt>null</tt> - deprecated. */
public String[] getValueNames() { return null; }
// PRIVATE //
private static Map<String, FakeSession> fSessions = new LinkedHashMap<String, FakeSession>();
private static int fSessionIdGenerator;
/** Not secure. Returns a simple, incremented integer. */
private static synchronized String getNextSessionId(){
fSessionIdGenerator++;
return new Integer(fSessionIdGenerator).toString();
}
/** Returns <tt>null</tt> if fails to join an existing session. */
private static synchronized HttpSession tryToJoinExistingSession(String aSessionId){
HttpSession result = null;
FakeSession existingSession = fSessions.get(aSessionId);
if ( existingSession != null ) {
existingSession.updateTimes();
if( existingSession.hasTimedOut() ) {
existingSession.invalidate();
}
else {
//successfully joined
existingSession.fIsPropsective = false;
result = existingSession;
}
}
return result;
}
private static synchronized HttpSession createNewSession(){
String id = getNextSessionId();
FakeSession result = new FakeSession(id);
fSessions.put(id, result);
return result;
}
private ServletContext fServletContext;
private long fCreationTime;
private String fSessionId;
private boolean fIsPropsective = true;
private long fLastAccessTime;
private long fCurrentAccessTime;
private int fMaxInactiveInterval = 60*30; // 30 minutes by default
private Map<String, Object> fAttributes = new LinkedHashMap<String, Object>();
private boolean fHasBeenInvalidated = false;
private static final Logger fLogger = Util.getLogger(FakeSession.class);
private FakeSession(String aSessionId){
fCreationTime = System.currentTimeMillis();
fLastAccessTime = fCreationTime;
fSessionId = aSessionId;
try {
fServletContext = new FakeServletContext();
}
catch(FileNotFoundException ex){
throw new IllegalArgumentException("Cannot construct FakeServletContext.");
}
}
private void vomitIfInvalidated() {
if (fHasBeenInvalidated) throw new IllegalStateException("Session has been invalidated.");
}
/** Called only when attempting to join an existing session. */
private void updateTimes(){
//a kind of moving forward of a time interval, with a start and end
fLastAccessTime = fCurrentAccessTime; //that is, the 'old' value from a previous request
fCurrentAccessTime = System.currentTimeMillis();
}
private boolean hasTimedOut(){
return (fCurrentAccessTime - fLastAccessTime) > fMaxInactiveInterval * 1000;
}
}