/*
 * Created on Nov 23, 2004
 */
package uk.org.ponder.rsf.state;

import uk.org.ponder.errorutil.TargettedMessage;
import uk.org.ponder.errorutil.TargettedMessageList;
import uk.org.ponder.errorutil.ThreadErrorState;
import uk.org.ponder.hashutil.EighteenIDGenerator;
import uk.org.ponder.rsf.request.RequestSubmittedValueCache;
import uk.org.ponder.rsf.request.SubmittedValueEntry;
import uk.org.ponder.rsf.viewstate.ViewParameters;
import uk.org.ponder.util.Logger;

/**
 * Holds the token-related state which is stored DURING a request. After the
 * request is completed, some of this information may be copied to a
 * TokenRequestState entry for the token, stored in the TokenStateHolder.
 * 
 * Keyed by a token which identifies a view that has been presented to the user.
 * The token is allocated afresh on each POST, and redirected to a GET view that
 * shares the same token. If the POST is non-erroneous, the rsvc can be blasted,
 * however if it is in error, it will be kept so that the values can be
 * resubmitted back to the user. We should CHANGE the old behaviour that carried
 * along the old token - we simply want to make a new token and erase the entry
 * for the old one.
 * 
 * When a further POST is made again from that view, it should mark that token
 * as USED, possibly by simply removing the values from the cache.
 * 
 * When the user tries to make a submission from an erased token, there should
 * be some (ideally) application-defined behaviour. But in general we don't have
 * much option but to erase their data, since it will probably conflict somehow.
 * 
 * NB RequestStateEntry is now a request-scope bean.
 * 
 * @author Antranig Basman (antranig@caret.cam.ac.uk)
 * 
 */
public class ErrorStateManager {
  /**
   * The id of the submitting control responsible for this POST cycle, if there
   * is one. This will be used to target the error messages delivered to the
   * following RENDER cycle.
   */
  public String globaltargetid = null;
  // this field will be set if there is an incoming error state.
  public ErrorTokenState errorstate;

  private TokenStateHolder errortsholder;
  // for a GET request this will be set, but empty.
  private RequestSubmittedValueCache requestrsvc;
  private ViewParameters viewparams;

  public void setTSHolder(TokenStateHolder errortsholder) {
    this.errortsholder = errortsholder;
  }

  public void setViewParameters(ViewParameters viewparams) {
    this.viewparams = viewparams;
  }

  public void init() {
    if (viewparams.errortoken != null) {
      ErrorTokenState storederrorstate = (ErrorTokenState) errortsholder
          .getTokenState(viewparams.errortoken);
      if (storederrorstate == null) {
        Logger.log.warn("Client requested error state " + viewparams.errortoken
            + " which has expired from the cache");
      }
      else {
        errorstate = storederrorstate;
        return;
      }
    }
    errorstate = new ErrorTokenState();
  }
  
  public TargettedMessageList getTargettedMessageList() {
    return errorstate.errors;
  }

  public void setRequestRSVC(RequestSubmittedValueCache requestrsvc) {
    this.requestrsvc = requestrsvc;
  }

  private static EighteenIDGenerator idgenerator = new EighteenIDGenerator();

  public String allocateToken() {
    return idgenerator.generateID();
  }

  public String allocateOutgoingToken() {
    if (errorstate.tokenID == null) {
      errorstate.tokenID = viewparams.errortoken == null ? allocateToken()
          : viewparams.errortoken;
    }
    return errorstate.tokenID;
  }

  // Convert errors which are currently referring to bean paths back onto
  // their fields as specified in RSVC. Called at the END of a POST cycle.
  private void fixupErrors(TargettedMessageList tml,
      RequestSubmittedValueCache rsvc) {
    for (int i = 0; i < tml.size(); ++i) {
      TargettedMessage tm = tml.messageAt(i);
      if (!tm.targetid.equals(TargettedMessage.TARGET_NONE)) {
        // Target ID refers to bean path. We need somewhat more flexible
        // rules for locating a component ID, which is defined as something
        // which follows "message-for". These IDs may actually be "synthetic",
        // at a particular level of containment, in that they refer to a
        // specially
        // instantiated genuine component which has the same ID.
        SubmittedValueEntry sve = rsvc.byPath(tm.targetid);
        String rewritten = TargettedMessage.TARGET_NONE;
        if (sve == null || sve.componentid == null) {
          // TODO: We want to trace EL reference chains BACKWARDS to figure
          // "ultimate source" of erroneous data. For now we will default to
          // TARGET_NONE
          Logger.log.warn("Message queued for non-component path "
              + tm.targetid);
        }
        else {
          rewritten = sve.componentid;

        }
        tm.targetid = rewritten;
      }
      // We desire TMs stored between cycles are "trivially" serializable, any
      // use of the actual exception object should be finished by action end.
      tm.exception = null;
    }
  }

  /**
   * Signal that all errors for this request cycle have been accumulated into
   * the current ese. It will now be cached in the tokenrequeststate for
   * reference by further requests, under the OUTGOING token ID, and cleared
   * from the current thread.
   * 
   * This is called at the end of both render and action cycles.
   * 
   * @return The error token ID to be used for the outgoing request, or null if
   *         there is no error.
   */
  public String requestComplete() {
    if (ThreadErrorState.isError()) {
      TargettedMessageList errors = ThreadErrorState.getErrorState().errors;
      // the errors arose from this cycle, and hence must be referred to
      // by SVEs from this cycle. If it is a GET cycle, rsvc will be empty,
      // but then all errors will have global target.
      fixupErrors(errors, requestrsvc);

      allocateOutgoingToken();
      errorstate.globaltargetid = globaltargetid;
      errorstate.rsvc = requestrsvc;
      errorstate.errors = errors;
      Logger.log.info(errorstate.rsvc.entries.size()
          + " RSVC values stored under error token " + errorstate.tokenID);
      errortsholder.putTokenState(errorstate.tokenID, errorstate);
      return errorstate.tokenID;
    }
    else {
      if (errorstate.tokenID != null) {
        errortsholder.clearTokenState(errorstate.tokenID);
      }
      return null;
    }
  }

}

@@ -2536,31 +2536,22 @@
 %7B%0A      
-ErrorTokenState
+Object
  storede
@@ -2563,28 +2563,11 @@
 tate
+o
  =
- (ErrorTokenState)
  err
@@ -2652,24 +2652,25 @@
 rorstate
+o
  == null
 ) %7B%0A    
@@ -2653,35 +2653,143 @@
 orstateo == null
-) %7B
+ %7C%7C %0A          !(storederrorstateo instanceof ErrorTokenState)) %7B%0A        // it may be from a stale ClassLoader
 %0A        Logger.
@@ -2905,16 +2905,78 @@
 ache%22);%0A
+        errortsholder.clearTokenState(viewparams.errortoken);%0A
       %7D%0A
@@ -3008,16 +3008,34 @@
 rstate =
+ (ErrorTokenState)
  storede
@@ -3043,16 +3043,17 @@
 rorstate
+o
 ;%0A      

Fix for unrecognisable ESE recovered from previous ClassLoader