001: /*
002: * Created on Nov 2, 2005
003: */
004: package uk.org.ponder.rsf.componentprocessor;
005:
006: import java.util.Iterator;
007:
008: import uk.org.ponder.beanutil.IterableBeanLocator;
009: import uk.org.ponder.rsf.components.ComponentList;
010: import uk.org.ponder.rsf.components.UIBound;
011: import uk.org.ponder.rsf.components.UICommand;
012: import uk.org.ponder.rsf.components.UIComponent;
013: import uk.org.ponder.rsf.components.UIContainer;
014: import uk.org.ponder.rsf.components.UIForm;
015: import uk.org.ponder.rsf.components.UIParameter;
016: import uk.org.ponder.rsf.request.EarlyRequestParser;
017: import uk.org.ponder.rsf.request.SubmittedValueEntry;
018: import uk.org.ponder.rsf.util.RSFUtil;
019: import uk.org.ponder.rsf.viewstate.ViewParameters;
020: import uk.org.ponder.rsf.viewstate.ViewParamsCodec;
021: import uk.org.ponder.saxalizer.SAXalizerMappingContext;
022: import uk.org.ponder.stringutil.StringList;
023: import uk.org.ponder.util.Logger;
024:
025: /**
026: * A fixer to be run BEFORE the main form fixer, which implements the HTML/HTTP
027: * form model whereby ALL nested child controls of the form are submitted. This
028: * fixer will perform no action if it discovers that the "submittingcontrols"
029: * fields of {@link uk.org.ponder.rsf.components.UIForm} has already been filled
030: * in.
031: * <p>
032: * This fixer is HTML/HTTP SPECIFIC, and should not execute for other idioms. It
033: * typically executes as the very first in the pipeline if it executes at all.
034: * <p>
035: * Note that it also is responsible for setting the "Submitting Control" packed
036: * attribute for UICommand objects.
037: *
038: * @author Antranig Basman (antranig@caret.cam.ac.uk)
039: *
040: */
041: public class ContainmentFormChildFixer implements ComponentProcessor {
042:
043: private SAXalizerMappingContext mappingcontext;
044: private ViewParamsCodec vpcodec;
045: private FormModel formModel;
046:
047: public void setFormModel(FormModel formModel) {
048: this .formModel = formModel;
049: }
050:
051: public void setMappingContext(SAXalizerMappingContext mappingcontext) {
052: this .mappingcontext = mappingcontext;
053: }
054:
055: public void setViewParamsCodec(ViewParamsCodec vpcodec) {
056: this .vpcodec = vpcodec;
057: }
058:
059: public void processComponent(UIComponent toprocesso) {
060: if (toprocesso instanceof UIForm) {
061: UIForm toprocess = (UIForm) toprocesso;
062: if (toprocess.submittingcontrols == null) {
063: toprocess.submittingcontrols = new StringList();
064: registerContainer(toprocess, toprocess);
065: }
066: }
067: }
068:
069: private void registerComponent(UIForm toprocess, UIComponent child) {
070: // TODO: produce some useful diagnostic on an attempt to create a nested
071: // form. This is "presumably" forbidden in every dialect but HTML, and
072: // "certainly" forbidden in HTML, even though it actually seems to work
073: // in practice (apart from double-registration of SUBMITTING_CONTROL &c).
074: // The problem is there is currently no (portable) place for this
075: // housekeeping information since we abolished FormModel.
076: if (child instanceof UIBound) {
077: boolean getform = toprocess.type
078: .equals(EarlyRequestParser.RENDER_REQUEST);
079: // in the case of an "unmanaged" form, this will generate submitting names
080: // that the processor is "not expecting" in repetitious cases.
081: UIBound bound = (UIBound) child;
082: String fullID = child.getFullID();
083: if (bound.willinput) {
084: String formID = toprocess.getFullID();
085:
086: if (getform) {
087: if (bound.valuebinding != null) {
088: ViewParameters viewparams = toprocess.viewparams;
089: String attrname = vpcodec.getMappingInfo(
090: toprocess.viewparams).pathToAttribute(
091: bound.valuebinding.value);
092: if (attrname == null) {
093: Logger.log
094: .warn("Warning: Unable to look up path "
095: + bound.valuebinding.value
096: + " in ViewParameters "
097: + viewparams.getClass()
098: + " with parseSpec "
099: + viewparams.getParseSpec()
100: + ": falling back to ID-based strategy");
101: }
102: bound.submittingname = attrname;
103: }
104: if (bound.submittingname == null) {
105: bound.submittingname = reduceGETSubmittingName(inferBaseSubmittingName(
106: fullID, formID));
107: }
108: } else {
109: bound.submittingname = fullID;
110: }
111: toprocess.submittingcontrols.add(fullID);
112: formModel.registerChild(toprocess, bound);
113: } else {
114: // case of a non-inputting control that needs to be nonetheless located
115: // on the client side. This is actually the non-HTML default.
116: // bound.submittingname = fullID;
117: // new interpretation - willinput = NO NAME
118: }
119: }
120: // TODO: clarify UIForm/UICommand relationship for WAP-style forms.
121: // Who is to be master!
122: // we expect that UIForm -> do, UICommand -> go
123: // http://www.codehelp.co.uk/html/wap1.html
124: else if (child instanceof UICommand) {
125: toprocess.submittingcontrols.add(child.getFullID());
126: formModel.registerChild(toprocess, child);
127: // add the notation explaining which control is submitting, when it does
128: UICommand command = (UICommand) child;
129: command.parameters.add(new UIParameter(
130: SubmittedValueEntry.SUBMITTING_CONTROL, child
131: .getFullID()));
132: if (command.methodbinding != null) {
133: command.parameters.add(new UIParameter(
134: SubmittedValueEntry.FAST_TRACK_ACTION,
135: command.methodbinding.value));
136: }
137: }
138: if (child instanceof UIContainer) {
139: registerContainer(toprocess, (UIContainer) child);
140: }
141: IterableBeanLocator children = new ComponentChildIterator(
142: child, mappingcontext);
143: for (Iterator childit = children.iterator(); childit.hasNext();) {
144: UIComponent nested = (UIComponent) children
145: .locateBean((String) childit.next());
146: registerComponent(toprocess, nested);
147: }
148: }
149:
150: private static String inferBaseSubmittingName(String fullID,
151: String formID) {
152: return fullID.substring(RSFUtil.commonPath(fullID, formID));
153: }
154:
155: private static String reduceGETSubmittingName(String submittingname) {
156: String[] comps = submittingname.split(":", -1);
157: submittingname = "";
158: for (int i = 0; i < comps.length; ++i) {
159: // append prefix.localID. for each component that has one
160: if ((i % 3) == 2) {
161: if (comps[i].length() != 0)
162: submittingname += comps[i - 2] + "." + comps[i]
163: + ".";
164: }
165: }
166: submittingname += comps[comps.length - 1];
167: // slight "hack" to make cluster components in GET forms work
168: // correctly - presumably there is only ONE of them that will actually
169: // try to submit an HTML value.
170: int hypos = submittingname.indexOf('-');
171: if (hypos != -1) {
172: submittingname = submittingname.substring(0, hypos);
173: }
174: return submittingname;
175: }
176:
177: private void registerContainer(UIForm toprocess,
178: UIContainer toregister) {
179: ComponentList children = toregister.flattenChildren();
180: for (int i = 0; i < children.size(); ++i) {
181: UIComponent child = children.componentAt(i);
182: registerComponent(toprocess, child);
183: }
184: }
185: }
|