001: /*
002: * Created on Nov 11, 2005
003: */
004: package uk.org.ponder.rsf.request;
005:
006: import uk.org.ponder.conversion.ConvertUtil;
007: import uk.org.ponder.rsf.components.ELReference;
008: import uk.org.ponder.rsf.components.UIBound;
009: import uk.org.ponder.rsf.components.UIDeletionBinding;
010: import uk.org.ponder.rsf.components.UIELBinding;
011: import uk.org.ponder.rsf.components.UIParameter;
012: import uk.org.ponder.rsf.uitype.UIType;
013: import uk.org.ponder.rsf.uitype.UITypes;
014: import uk.org.ponder.saxalizer.SAXalXMLProvider;
015:
016: /*
017: * Manages the (to some extent RenderSystem dependent) process of converting
018: * bindings (of three types - fossilized, deletion and pure EL) into String
019: * key/value pairs suitable for transit over HTTP.
020: *
021: * <p>In case of an HTTP submission, these are encoded as key/value in the
022: * request map (via hidden form fields) as follows: <br>key =
023: * componentid-fossil, value=[i|o]uitype-name#{bean.member}oldvalue <br>Alternatively,
024: * this SVE may represent a "fast EL" binding, without a component. In this
025: * case, it has the form <br>key = [deletion|el]-binding, value =
026: * [e|o]#{el.lvalue}rvalue, where rvalue may represent an EL rvalue, a
027: * SAXLeafType or a Object. <br>The actual value submission is encoded in the
028: * RenderSystem for UIInputBase, but is generally expected to simply follow <br>key =
029: * componentid, value = newvalue.
030: */
031:
032: public class FossilizedConverter {
033: public static final char INPUT_COMPONENT = 'i';
034: public static final char INPUT_COMPONENT_MUSTAPPLY = 'j';
035: public static final char OUTPUT_COMPONENT = 'o';
036: public static final char EL_BINDING = 'e';
037: public static final char OBJECT_BINDING = 'o';
038: /**
039: * The suffix appended to the component fullID in order to derive the key for
040: * its corresponding fossilized binding.
041: */
042: public static final String FOSSIL_SUFFIX = "-fossil";
043: /**
044: * A suffix to be used for the "componentless" bindings defined by
045: * UIDeletionBinding and UIELBinding.
046: */
047: public static final String BINDING_SUFFIX = "-binding";
048: public static final String RESHAPER_SUFFIX = "-reshaper";
049: public static final String DELETION_KEY = "deletion"
050: + BINDING_SUFFIX;
051: public static final String VALUE_DELETION_KEY = "valuedeletion"
052: + BINDING_SUFFIX;
053: public static final String ELBINDING_KEY = "el" + BINDING_SUFFIX;
054: public static final String VIRTUAL_ELBINDING_KEY = "virtual-"
055: + ELBINDING_KEY;
056:
057: private SAXalXMLProvider xmlprovider;
058:
059: public static final String COMMAND_LINK_PARAMETERS = "command link parameters";
060:
061: public void setSAXalXMLProvider(SAXalXMLProvider xmlprovider) {
062: this .xmlprovider = xmlprovider;
063: }
064:
065: /**
066: * A utility method to determine whether a given key (from the request map)
067: * represents a Fossilised binding, i.e. it ends with the suffix
068: * {@link #FOSSIL_SUFFIX}.
069: */
070: public boolean isFossilisedBinding(String key) {
071: return key.endsWith(FOSSIL_SUFFIX);
072: }
073:
074: public boolean isNonComponentBinding(String key) {
075: // TODO: After 0.7.0 reform the bindings encoding system so that
076: // virtual bindings can be nameless.
077: return key.endsWith(BINDING_SUFFIX)
078: && !key.equals(VIRTUAL_ELBINDING_KEY);
079: }
080:
081: /** Parse a "non-component binding" key/value pair * */
082: public SubmittedValueEntry parseBinding(String key, String value) {
083: SubmittedValueEntry togo = new SubmittedValueEntry();
084: togo.isEL = value.charAt(0) == EL_BINDING;
085: int endcurly = findEndCurly(value);
086: togo.valuebinding = value.substring(3, endcurly);
087: togo.newvalue = value.substring(endcurly + 1);
088: if (key.equals(DELETION_KEY)) {
089: togo.isdeletion = true;
090: togo.newvalue = null;
091: } else if (key.equals(VALUE_DELETION_KEY)) {
092: togo.isdeletion = true;
093: }
094: // such a binding will hit the data model via the RSVCApplier.
095: return togo;
096: }
097:
098: /**
099: * Attempts to construct a SubmittedValueEntry on a key/value pair found in
100: * the request map, for which isFossilisedBinding has already returned
101: * <code>true</code>. In order to complete this (non-deletion) entry, the
102: * <code>newvalue</code> field must be set separately.
103: *
104: * @param key
105: * @param value
106: */
107: public SubmittedValueEntry parseFossil(String key, String value) {
108: SubmittedValueEntry togo = new SubmittedValueEntry();
109:
110: togo.mustapply = value.charAt(0) == INPUT_COMPONENT_MUSTAPPLY;
111: int firsthash = value.indexOf('#');
112: String uitypename = value.substring(1, firsthash);
113: int endcurly = findEndCurly(value);
114: togo.valuebinding = value.substring(firsthash + 2, endcurly);
115: String oldvaluestring = value.substring(endcurly + 1);
116:
117: UIType uitype = UITypes.forName(uitypename);
118: if (oldvaluestring.length() > 0) {
119: Class uiclass = uitype == null ? null : uitype
120: .getPlaceholder().getClass();
121: togo.oldvalue = ConvertUtil.parse(oldvaluestring,
122: xmlprovider, uiclass);
123: } else {
124: // must ensure that we record the TYPE of oldvalue here for later use by
125: // fixupNewValue, even if the oldvalue is empty.
126: togo.oldvalue = uitype.getPlaceholder();
127: }
128:
129: togo.componentid = key.substring(0, key.length()
130: - FOSSIL_SUFFIX.length());
131:
132: return togo;
133: }
134:
135: private int findEndCurly(String value) {
136: for (int i = 0; i < value.length(); ++i) {
137: char c = value.charAt(i);
138: if (c == '}' && (i == 0 || value.charAt(i - 1) != '\\'))
139: return i;
140: }
141: return -1;
142: }
143:
144: public String computeBindingValue(String lvalue, Object rvalue) {
145: if (rvalue instanceof ELReference) {
146: ELReference elref = (ELReference) rvalue;
147: return EL_BINDING + "#{" + lvalue + "}" + elref.value;
148: } else {
149: // The value type will be inferred on delivery, for Object bindings.
150: return OBJECT_BINDING
151: + "#{"
152: + lvalue
153: + "}"
154: + (rvalue == null ? "" : ConvertUtil.render(rvalue,
155: xmlprovider));
156: }
157: }
158:
159: public void computeDeletionBinding(UIDeletionBinding binding) {
160: if (binding.deletetarget == null) {
161: binding.name = DELETION_KEY;
162: binding.value = OBJECT_BINDING + "#{"
163: + binding.deletebinding.value + "}";
164: } else {
165: binding.name = VALUE_DELETION_KEY;
166: binding.value = computeBindingValue(
167: binding.deletebinding.value, binding.deletetarget);
168: }
169: }
170:
171: public void computeELBinding(UIELBinding binding) {
172: binding.name = binding.virtual ? VIRTUAL_ELBINDING_KEY
173: : ELBINDING_KEY;
174: binding.value = computeBindingValue(binding.valuebinding.value,
175: binding.rvalue);
176: }
177:
178: /**
179: * Computes the fossilised binding parameter that needs to be added to forms
180: * for which the supplied UIBound is a submitting control. The value of the
181: * bound component is one of the (three) UITypes, or else an unknown non-leaf
182: * type. This value will be serialized and added to the end of the binding.
183: */
184: // NB! UIType is hardwired to use the static StringArrayParser, to avoid a
185: // wireup graph cycle of FossilizedConverter on the HTMLRenderSystem.
186: public UIParameter computeFossilizedBinding(UIBound togenerate,
187: Object modelvalue) {
188: if (!togenerate.fossilize) {
189: throw new IllegalArgumentException(
190: "Cannot compute fossilized binding "
191: + "for non-fossilizing component with ID "
192: + togenerate.getFullID());
193: }
194: UIParameter togo = new UIParameter();
195: togo.virtual = !togenerate.willinput;
196: togo.name = togenerate.submittingname + FOSSIL_SUFFIX;
197: String oldvaluestring = null;
198: Object oldvalue = modelvalue == null ? togenerate
199: .acquireValue() : modelvalue;
200: if (oldvalue == null) {
201: throw new IllegalArgumentException(
202: "Error: cannot compute fossilized binding for component with full ID "
203: + togenerate.getFullID()
204: + " since bound value is null");
205:
206: }
207: UIType type = UITypes.forObject(oldvalue);
208:
209: if (type != null) {
210: // don't try to write as a leaf type, since the the parser (above) will
211: // not have enough context to infer the type above.
212: oldvaluestring = xmlprovider.getMappingContext().saxleafparser
213: .render(oldvalue);
214: } else {
215: oldvaluestring = xmlprovider.toString(oldvalue);
216: }
217: String typestring = type == null ? "" : type.getName();
218: togo.value = (togenerate.mustapply ? INPUT_COMPONENT_MUSTAPPLY
219: : (togenerate.willinput ? INPUT_COMPONENT
220: : OUTPUT_COMPONENT))
221: + typestring
222: + "#{"
223: + togenerate.valuebinding.value
224: + "}" + oldvaluestring;
225: return togo;
226: }
227:
228: public String getReshaperKey(String componentid) {
229: return componentid + RESHAPER_SUFFIX;
230: }
231:
232: public UIParameter computeReshaperBinding(UIBound togenerate) {
233: if (togenerate.darreshaper == null) {
234: return null;
235: } else {
236: UIParameter togo = new UIParameter();
237: togo.name = getReshaperKey(togenerate.submittingname);
238: togo.value = togenerate.darreshaper.value;
239: return togo;
240: }
241: }
242:
243: /**
244: * Fixes up the supplied "new value" relative to information discovered in the
245: * fossilized binding. After this point, newvalue is authoritatively not null
246: * for every submission where a component which was marked as "expecting
247: * input" SHOULD be receiving data, and of the same type as oldvalue.
248: * <p>
249: * In the case of an already "erroneous fossilization" where oldvalue was
250: * itself empty/null, it is possible that no submission returns, and none is
251: * actually possible (e.g. selection controls which have no parent choices).
252: * In this case newvalue will remain null (semantically null rather than
253: * submittedly null).
254: * <p>
255: * Note that oldvalue is now always not null here.
256: *
257: * @param value The value assigned to the fossilized binding
258: */
259: public void fixupNewValue(SubmittedValueEntry sve,
260: RenderSystemDecoder rendersystemstatic, String key,
261: String value) {
262: char typechar = value.charAt(0);
263: if (typechar == INPUT_COMPONENT
264: || typechar == INPUT_COMPONENT_MUSTAPPLY) {
265: rendersystemstatic.fixupUIType(sve);
266: Class requiredclass = sve.oldvalue.getClass();
267: if (sve.newvalue != null
268: && sve.newvalue.getClass() != requiredclass) {
269: // no attempt to catch the exceptions from the next two lines since they
270: // all represent assertion errors.
271: String[] newvalues = (String[]) sve.newvalue;
272: sve.newvalue = xmlprovider.getMappingContext().saxleafparser
273: .parse(requiredclass, newvalues[0]);
274: // The only non-erroneous case here is where newvalue is String[], and
275: // oldvalue is some scalar type. Should new UITypes arise, this will
276: // need to be reviewed.
277: }
278:
279: }
280:
281: }
282:
283: }
|