001: /*
002: * Created on Nov 23, 2004
003: */
004: package uk.org.ponder.rsf.state;
005:
006: import uk.org.ponder.hashutil.EighteenIDGenerator;
007: import uk.org.ponder.messageutil.TargettedMessage;
008: import uk.org.ponder.messageutil.TargettedMessageList;
009: import uk.org.ponder.rsf.request.RequestSubmittedValueCache;
010: import uk.org.ponder.rsf.request.SubmittedValueEntry;
011: import uk.org.ponder.rsf.viewstate.ViewParameters;
012: import uk.org.ponder.util.Logger;
013:
014: /**
015: * Holds the token-related state which is stored DURING a request. After the
016: * request is completed, some of this information may be copied to a
017: * TokenRequestState entry for the token, stored in the TokenStateHolder.
018: *
019: * Keyed by a token which identifies a view that has been presented to the user.
020: * The token is allocated afresh on each POST, and redirected to a GET view that
021: * shares the same token. If the POST is non-erroneous, the rsvc can be blasted,
022: * however if it is in error, it will be kept so that the values can be
023: * resubmitted back to the user. We should CHANGE the old behaviour that carried
024: * along the old token - we simply want to make a new token and erase the entry
025: * for the old one.
026: *
027: * When a further POST is made again from that view, it should mark that token
028: * as USED, possibly by simply removing the values from the cache.
029: *
030: * When the user tries to make a submission from an erased token, there should
031: * be some (ideally) application-defined behaviour. But in general we don't have
032: * much option but to erase their data, since it will probably conflict somehow.
033: *
034: * NB RequestStateEntry is now a request-scope bean.
035: *
036: * @author Antranig Basman (antranig@caret.cam.ac.uk)
037: *
038: */
039: public class ErrorStateManager {
040: /**
041: * The id of the submitting control responsible for this POST cycle, if there
042: * is one. This will be used to target the error messages delivered to the
043: * following RENDER cycle.
044: */
045: public String globaltargetid = null;
046: // this field will be set if there is an incoming error state.
047: public ErrorTokenState errorstate;
048:
049: private TokenStateHolder errortsholder;
050: // for a GET request this will be set, but empty.
051: private RequestSubmittedValueCache requestrsvc;
052: private ViewParameters viewparams;
053: private TargettedMessageList messages;
054: private String outgoingToken;
055:
056: public void setTargettedMessageList(TargettedMessageList tml) {
057: this .messages = tml;
058: }
059:
060: public TargettedMessageList getTargettedMessageList() {
061: return messages;
062: }
063:
064: public void setTSHolder(TokenStateHolder errortsholder) {
065: this .errortsholder = errortsholder;
066: }
067:
068: public void setViewParameters(ViewParameters viewparams) {
069: this .viewparams = viewparams;
070: }
071:
072: public void init() {
073: if (viewparams.errortoken != null) {
074: Object storederrorstateo = errortsholder
075: .getTokenState(viewparams.errortoken);
076: if (storederrorstateo == null
077: || !(storederrorstateo instanceof ErrorTokenState)) {
078: // it may be from a stale ClassLoader
079: Logger.log.warn("Client requested error state "
080: + viewparams.errortoken
081: + " which has expired from the cache");
082: errortsholder.clearTokenState(viewparams.errortoken);
083: } else {
084: errorstate = (ErrorTokenState) storederrorstateo;
085: messages.addMessages(errorstate.messages);
086: return;
087: }
088: }
089: errorstate = new ErrorTokenState();
090: }
091:
092: public void setRequestRSVC(RequestSubmittedValueCache requestrsvc) {
093: this .requestrsvc = requestrsvc;
094: }
095:
096: private static EighteenIDGenerator idgenerator = new EighteenIDGenerator();
097:
098: public String allocateToken() {
099: return idgenerator.generateID();
100: }
101:
102: public String allocateOutgoingToken() {
103: if (outgoingToken == null) {
104: outgoingToken = errorstate.tokenID = viewparams.errortoken == null ? allocateToken()
105: : viewparams.errortoken;
106: }
107: return errorstate.tokenID;
108: }
109:
110: // Convert errors which are currently referring to bean paths back onto
111: // their fields as specified in RSVC. Called at the END of a POST cycle.
112: private void fixupMessages(TargettedMessageList tml,
113: RequestSubmittedValueCache rsvc) {
114: if (outgoingToken == null) {
115: return; // Do not fix up if the errors were not from this cycle
116: }
117: for (int i = 0; i < tml.size(); ++i) {
118: TargettedMessage tm = tml.messageAt(i);
119: if (!tm.targetid.equals(TargettedMessage.TARGET_NONE)) {
120: // Target ID refers to bean path. We need somewhat more flexible
121: // rules for locating a component ID, which is defined as something
122: // which follows "message-for". These IDs may actually be "synthetic",
123: // at a particular level of containment, in that they refer to a
124: // specially
125: // instantiated genuine component which has the same ID.
126: SubmittedValueEntry sve = rsvc.byPath(tm.targetid);
127: String rewritten = TargettedMessage.TARGET_NONE;
128: if (sve == null || sve.componentid == null) {
129: // TODO: We want to trace EL reference chains BACKWARDS to figure
130: // "ultimate source" of erroneous data. For now we will default to
131: // TARGET_NONE
132: Logger.log
133: .warn("Message queued for non-component path "
134: + tm.targetid);
135: } else {
136: rewritten = sve.componentid;
137:
138: }
139: tm.targetid = rewritten;
140: }
141: // We desire TMs stored between cycles are "trivially" serializable, any
142: // use of the actual exception object should be finished by action end.
143: tm.exception = null;
144: }
145: }
146:
147: /**
148: * Signal that all errors for this request cycle have been accumulated into
149: * the current ese. It will now be cached in the tokenrequeststate for
150: * reference by further requests, under the OUTGOING token ID, and cleared
151: * from the current thread.
152: *
153: * This is called at the end of both render and action cycles.
154: *
155: * @return The error token ID to be used for the outgoing request, or null if
156: * there is no error.
157: */
158: public String requestComplete() {
159: // If tokenID is null, it is probably an odd condition where we are on a
160: // GET cycle and the errors are not for this view (perhaps SpringMVC)
161: if (messages.size() > 0 && errorstate.tokenID != null) {
162:
163: // the errors arose from this cycle, and hence must be referred to
164: // by SVEs from this cycle. If it is a GET cycle, rsvc will be empty,
165: // but then all errors will have global target.
166: fixupMessages(messages, requestrsvc);
167:
168: errorstate.globaltargetid = globaltargetid;
169: if (messages.isError()) {
170: // do not store the rsvc if no error, submitted values were accepted
171: // we are propagating messages only
172: errorstate.rsvc = requestrsvc.copy();
173: }
174: errorstate.messages = messages;
175: if (errorstate.rsvc != null) {
176: Logger.log.info(errorstate.rsvc.getEntries()
177: + " RSVC values stored under error token "
178: + errorstate.tokenID);
179: }
180: errortsholder.putTokenState(errorstate.tokenID, errorstate);
181: return errorstate.tokenID;
182: } else {
183: if (errorstate.tokenID != null) {
184: errortsholder.clearTokenState(errorstate.tokenID);
185: }
186: return null;
187: }
188: }
189:
190: }
|