package org.opengroupware.jope.appserver;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public abstract class WOSessionStore implements JoObject {
  protected final Log log = LogFactory.getLog("WOSessionStore");
  
  private static WOSessionStore serverSessionStore = new WOServerSessionStore();
  
  public static WOSessionStore serverSessionStore() {
    return serverSessionStore;
  }

  public abstract void saveSessionForContext(WOContext _ctx);
  public abstract WOSession removeSessionWithID(String _sid);
  public abstract WOSession restoreSessionForID(String _sid, WORequest _rq);
  
  /* checkin / checkout */
  
  protected Set<String> workingSet = new HashSet<String>(128);
  
  public WOSession checkOutSessionForID(String _sid, WORequest _rq) {
    if (_sid == null) {
      this.log.error("got no session-id for session checkout!");
      return null;
    }
    
    /* checkout session */
    
    boolean didCheckOut = false;
    for (int tryCount = 0; !didCheckOut && tryCount < 3; tryCount++) {
      synchronized (this.workingSet) {
        if (!this.workingSet.contains(_sid)) {
          this.workingSet.add(_sid);
          didCheckOut = true;
        }
      }
      
      /* wait a bit prior next attempt to allow other threads to complete */
      if (tryCount < 3 && !didCheckOut) {
        try {
          switch (tryCount) {
            case 0:  Thread.sleep( 200 /* ms */); break;
            case 1:  Thread.sleep( 500 /* ms */); break;
            default: Thread.sleep(1000 /* ms */); break;
          }
        }
        catch (InterruptedException e) {
          break;
        }
      }
    }
    
    if (!didCheckOut) {
      // TODO: maybe we should just block the thread?
      this.log.info("could not checkout session, already checked out: " + _sid);
      return null;
    }
    
    /* restore session */
    
    WOSession sn = null;
    try {
      sn = this.restoreSessionForID(_sid, _rq);
    }
    finally {
      /* checkin session if restoration failed */
      if (sn == null) {
        synchronized (this.workingSet) {
          this.workingSet.remove(_sid);
        }
      }
    }
    
    return sn;
  }

  public void checkInSessionForContext(WOContext _ctx) {
    if (_ctx == null) {
      this.log.error("got no context for session checkin!");
      return;
    }
    
    try {
      this.saveSessionForContext(_ctx);
    }
    finally {
      /* we checkin in any case */
      String sid = _ctx.hasSession() ? _ctx.session().sessionID() : null;
      if (sid != null) {
        synchronized (this.workingSet) {
          this.workingSet.remove(sid);
        }
      }
    }
  }
  
  /* JoObject */
  
  public Object lookupName(String _name, JoContext _ctx, boolean _aquire) {
    WORequest rq = null;
    if (_ctx instanceof WOContext)
      rq = ((WOContext)_ctx).request();

    // TODO: we need to implement some 'sleep' call or other callback to
    //       checkin the checked out session!
    return this.checkOutSessionForID(_name,rq);
  }
}
