001: /* Binding.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Thu Feb 1 17:13:40 2007, Created by Henri
010: }}IS_NOTE
011:
012: Copyright (C) 2006 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: }}IS_RIGHT
016: */
017: package org.zkoss.zkplus.databind;
018:
019: import org.zkoss.zul.impl.InputElement;
020:
021: import org.zkoss.zk.ui.Path;
022: import org.zkoss.zk.ui.Component;
023: import org.zkoss.zk.ui.UiException;
024: import org.zkoss.zk.ui.WrongValueException;
025: import org.zkoss.zk.ui.event.Event;
026: import org.zkoss.zk.ui.event.Events;
027: import org.zkoss.zk.ui.event.Express;
028: import org.zkoss.zk.ui.event.EventListener;
029: import org.zkoss.zk.ui.sys.ComponentsCtrl;
030: import org.zkoss.zk.ui.ext.DynamicPropertied;
031:
032: import org.zkoss.util.ModificationException;
033: import org.zkoss.lang.Classes;
034: import org.zkoss.lang.Objects;
035: import org.zkoss.lang.reflect.Fields;
036:
037: import java.lang.reflect.Method;
038: import java.io.Serializable;
039: import java.util.Map;
040: import java.util.Set;
041: import java.util.List;
042: import java.util.HashMap;
043: import java.util.Iterator;
044: import java.util.LinkedHashSet;
045: import java.util.ArrayList;
046:
047: /**
048: * A Data Binding that associate component+attr to an bean expression.
049: *
050: * @author Henri
051: * @since 3.0.0
052: */
053: public class Binding {
054: private DataBinder _binder;
055: private Component _comp;
056: private String _attr;
057: private String _expression; //the bean expression
058: private LinkedHashSet _loadWhenEvents;
059: private Set _saveWhenEvents;
060: private boolean _loadable = true;
061: private boolean _savable;
062: private TypeConverter _converter;
063: private String[] _paths; //bean reference path (a.b.c)
064:
065: /** Constrcutor to form a binding between UI component and backend data bean.
066: * @param binder the associated Data Binder.
067: * @param comp The concerned component
068: * @param attr The component attribute
069: * @param expr The bean expression.
070: * @param loadWhenEvents The event set when to load data.
071: * @param saveWhenEvents The event set when to save data.
072: * @param access In the view of UI component: "load" load only, "both" load/save,
073: * "save" save only when doing data binding. null means using the default access
074: * natural of the component. e.g. Label.expr is "load", but Textbox.expr is "both".
075: * @param converter The converter class used to convert classes between component attribute
076: * and the associated bean expression. null means using the default class conversion method.
077: */
078: /*package*/Binding(DataBinder binder, Component comp, String attr,
079: String expr, LinkedHashSet loadWhenEvents,
080: Set saveWhenEvents, String access, String converter) {
081: _binder = binder;
082: _comp = comp;
083: setAttr(attr);
084: setExpression(expr);
085: setLoadWhenEvents(loadWhenEvents);
086: setSaveWhenEvents(saveWhenEvents);
087: setAccess(access);
088: setConverter(converter);
089:
090: }
091:
092: /** Gets the associated Data Binder of this Binding.
093: */
094: public DataBinder getBinder() {
095: return _binder;
096: }
097:
098: /** Gets the associated Component of this Binding.
099: */
100: public Component getComponent() {
101: return _comp;
102: }
103:
104: /** Set component attribute name.
105: * @param attr component attribute.
106: */
107: /*package*/void setAttr(String attr) {
108: _attr = attr;
109: }
110:
111: /** Get component attribute name.
112: */
113: public String getAttr() {
114: return _attr;
115: }
116:
117: /** Set bean expression (a.b.c).
118: */
119: /*package*/void setExpression(String expr) {
120: _expression = expr;
121: _paths = parseBeanExpression(expr);
122: }
123:
124: //:TODO: handle function parsing
125: private String[] parseBeanExpression(String expr) {
126: String[] paths = new String[1];
127: paths[0] = expr;
128: return paths;
129: }
130:
131: /** Get bean expression, e.g. a.b.c.
132: */
133: public String getExpression() {
134: return _expression;
135: }
136:
137: /**
138: * Internal methods. DO NOT USE THIS.
139: * Get bean reference paths.
140: */
141: /*package*/String[] getPaths() {
142: return _paths;
143: }
144:
145: /** Set save-when event expression.
146: * @param saveWhenEvents the save-when expression.
147: */
148: /*package*/void setSaveWhenEvents(Set saveWhenEvents) {
149: _saveWhenEvents = saveWhenEvents;
150: }
151:
152: /** Get save-when event expression.
153: */
154: public Set getSaveWhenEvents() {
155: return _saveWhenEvents;
156: }
157:
158: /** Add load-when event expression.
159: * @param loadWhenEvent the load-when expression.
160: */
161: /*package*/void setLoadWhenEvents(LinkedHashSet loadWhenEvents) {
162: _loadWhenEvents = loadWhenEvents;
163: }
164:
165: /** Get load-when event expression set.
166: */
167: public LinkedHashSet getLoadWhenEvents() {
168: return _loadWhenEvents;
169: }
170:
171: /** Set accessibility.
172: */
173: /*package*/void setAccess(String access) {
174: if (access == null) { //default access to none
175: return;
176: }
177:
178: if ("both".equals(access)) {
179: _loadable = true;
180: _savable = true;
181: } else if ("load".equals(access)) {
182: _loadable = true;
183: _savable = false;
184: } else if ("save".equals(access)) {
185: _loadable = false;
186: _savable = true;
187: } else if ("none".equals(access)) { //unknow access mode
188: _loadable = false;
189: _savable = false;
190: } else {
191: throw new UiException(
192: "Unknown DataBinder access mode. Should be \"both\", \"load\", \"save\", or \"none\": "
193: + access);
194: }
195: }
196:
197: /** Whether the binding is loadable.
198: */
199: public boolean isLoadable() {
200: return _loadable;
201: }
202:
203: /** Whether the binding is savable.
204: */
205: public boolean isSavable() {
206: return _savable;
207: }
208:
209: /** Set the {@link TypeConverter}.
210: * @param cvtClsName the converter class name.
211: */
212: /*package*/void setConverter(String cvtClsName) {
213: if (cvtClsName != null) {
214: try {
215: _converter = (TypeConverter) Classes
216: .newInstanceByThread(cvtClsName);
217: } catch (Exception ex) {
218: throw UiException.Aide.wrap(ex);
219: }
220: }
221: }
222:
223: /** Get the {@link TypeConverter}.
224: */
225: public TypeConverter getConverter() {
226: return _converter;
227: }
228:
229: /** load bean value into the attribute of the specified component.
230: * @param comp the component.
231: */
232: public void loadAttribute(Component comp) {
233: if (!isLoadable() || _attr.startsWith("_")
234: || _binder.isTemplate(comp) || comp.getPage() == null) {
235: return; //cannot load, a control attribute, or a detached component, skip!
236: }
237: Object bean = _binder.getBeanAndRegisterBeanSameNodes(comp,
238: _expression);
239: myLoadAttribute(comp, bean);
240: }
241:
242: /** load bean value into the attribute of the specified component.
243: * @param comp the component.
244: * @param bean the bean value.
245: */
246: public void loadAttribute(Component comp, Object bean) {
247: if (!isLoadable() || _attr.startsWith("_")
248: || _binder.isTemplate(comp) || comp.getPage() == null) {
249: return; //cannot load, a control attribute, or a detached component, skip!
250: }
251: myLoadAttribute(comp, bean);
252: }
253:
254: private void myLoadAttribute(Component comp, Object bean) {
255: try {
256: if (_converter != null) {
257: bean = _converter.coerceToUi(bean, comp);
258: }
259:
260: //Bug #1876198 Error msg appears when load page (databind+CustomConstraint)
261: //catching WrongValueException no longer works, check special case and
262: //use setRowValue() method directly
263: if ((comp instanceof InputElement) && "value".equals(_attr)) {
264: Object value = bean;
265: try { //Bug 1879389
266: final Method m = comp.getClass().getMethod(
267: "getValue", null);
268: value = Classes.coerce(m.getReturnType(), bean);
269: } catch (NoSuchMethodException ex) { //ignore it
270: }
271: Fields.set(comp, "rawValue", value, _converter == null);
272: } else {
273: Fields.set(comp, _attr, bean, _converter == null);
274: }
275: } catch (ClassCastException ex) {
276: throw UiException.Aide.wrap(ex);
277: } catch (NoSuchMethodException ex) {
278: //Bug #1813278, Annotations do not work with xhtml tags
279: if (comp instanceof DynamicPropertied) {
280: final DynamicPropertied dpcomp = (DynamicPropertied) comp;
281: if (dpcomp.hasDynamicProperty(_attr)) {
282: //no way to know destination type of the property, use bean as is
283: dpcomp.setDynamicProperty(_attr, bean);
284: } else {
285: throw UiException.Aide.wrap(ex);
286: }
287: } else {
288: throw UiException.Aide.wrap(ex);
289: }
290: } catch (ModificationException ex) {
291: throw UiException.Aide.wrap(ex);
292:
293: //Bug #1876198 Error msg appears when load page (databind+CustomConstraint)
294: //catching WrongValueException no longer works, so mark it out
295: /*} catch (WrongValueException ex) {
296: //Bug #1615371, try to use setRawValue()
297: if ("value".equals(_attr)) {
298: try {
299: Fields.set(comp, "rawValue", bean, _converter == null);
300: } catch (Exception ex1) {
301: //exception
302: throw ex;
303: }
304: } else {
305: throw ex;
306: }
307: */
308: }
309: }
310:
311: /** save into bean value from the attribute of the specified component.
312: * @param comp the component.
313: */
314: public void saveAttribute(Component comp) {
315: final Object[] vals = getAttributeValues(comp);
316: saveAttributeValue(comp, vals, null);
317: }
318:
319: private void saveAttributeValue(Component comp, Object[] vals,
320: List loadOnSaveInfos) {
321: if (vals == null)
322: return;
323:
324: final Object val = vals[0];
325: final Object rawval = vals[1];
326: _binder.setBeanAndRegisterBeanSameNodes(comp, val, this ,
327: _expression, _converter == null, rawval,
328: loadOnSaveInfos);
329: }
330:
331: /** Get converted value and original value of this Binding.
332: */
333: private Object[] getAttributeValues(Component comp) {
334: if (!isSavable() || _attr.startsWith("_")
335: || _binder.isTemplate(comp) || comp.getPage() == null) {
336: return null; //cannot save, a control attribute, or a detached component, skip!
337: }
338: Object rawval = null;
339: try {
340: rawval = Fields.get(comp, _attr);
341: } catch (NoSuchMethodException ex) {
342: //Bug #1813278, Annotations do not work with xhtml tags
343: if (comp instanceof DynamicPropertied) {
344: final DynamicPropertied dpcomp = (DynamicPropertied) comp;
345: if (dpcomp.hasDynamicProperty(_attr)) {
346: rawval = dpcomp.getDynamicProperty(_attr);
347: } else {
348: throw UiException.Aide.wrap(ex);
349: }
350: } else {
351: throw UiException.Aide.wrap(ex);
352: }
353: }
354: try {
355: final Object val = (_converter == null) ? rawval
356: : _converter.coerceToBean(rawval, comp);
357: return new Object[] { val, rawval };
358: } catch (ClassCastException ex) {
359: throw UiException.Aide.wrap(ex);
360: }
361: }
362:
363: /*package*/void registerSaveEvents(Component comp) {
364: if (_saveWhenEvents != null && isSavable()) { //bug 1804356
365: for (final Iterator it = _saveWhenEvents.iterator(); it
366: .hasNext();) {
367: final String expr = (String) it.next();
368: final Object[] objs = ComponentsCtrl
369: .parseEventExpression(comp, expr, comp, false);
370: //objs[0] component, objs[1] event name
371: final Component target = (Component) objs[0];
372: final String evtname = (String) objs[1];
373:
374: SaveEventListener listener = (SaveEventListener) target
375: .getAttribute("zk.SaveEventListener." + evtname);
376: if (listener == null) {
377: listener = new SaveEventListener();
378: target.setAttribute("zk.SaveEventListener."
379: + evtname, listener);
380: target.addEventListener(evtname, listener);
381: }
382: listener.addDataTarget(this , comp);
383: }
384: }
385: }
386:
387: /*package*/void registerLoadEvents(Component comp) {
388: if (_loadWhenEvents != null && isLoadable()) { //bug 1804356
389: for (final Iterator it = _loadWhenEvents.iterator(); it
390: .hasNext();) {
391: final String expr = (String) it.next();
392: final Object[] objs = ComponentsCtrl
393: .parseEventExpression(comp, expr, comp, false);
394: //objs[0] component, objs[1] event name
395: final Component target = (Component) objs[0];
396: final String evtname = (String) objs[1];
397:
398: LoadEventListener listener = (LoadEventListener) target
399: .getAttribute("zk.LoadEventListener." + evtname);
400: if (listener == null) {
401: listener = new LoadEventListener();
402: target.setAttribute("zk.LoadEventListener."
403: + evtname, listener);
404: target.addEventListener(evtname, listener);
405: }
406: listener.addDataTarget(this , comp);
407: }
408: }
409: }
410:
411: //-- Object --//
412: public String toString() {
413: return "[binder:" + _binder + ", comp:" + _comp + ", attr:"
414: + _attr + ", expr:" + _expression + ", load-when:"
415: + _loadWhenEvents + ", save-when:" + _saveWhenEvents
416: + ", load:" + _loadable + ", save:" + _savable
417: + ", converter:" + _converter + "]";
418: }
419:
420: private static class BindingInfo implements Serializable {
421: private Binding _binding;
422: private Component _comp;
423: private Object[] _vals;
424:
425: public BindingInfo(Binding binding, Component comp,
426: Object[] vals) {
427: _binding = binding;
428: _comp = comp;
429: _vals = vals;
430: }
431:
432: public Component getComponent() {
433: return _comp;
434: }
435:
436: public Binding getBinding() {
437: return _binding;
438: }
439:
440: public Object[] getAttributeValues() {
441: return _vals;
442: }
443: }
444:
445: private static abstract class BaseEventListener implements
446: EventListener, Express {
447: protected List _dataTargets;
448:
449: public BaseEventListener() {
450: _dataTargets = new ArrayList(8);
451: }
452:
453: public void addDataTarget(Binding binding, Component comp) {
454: _dataTargets.add(new BindingInfo(binding, comp, null));
455: }
456: }
457:
458: private static class LoadEventListener extends BaseEventListener {
459: public LoadEventListener() {
460: super ();
461: }
462:
463: public void onEvent(Event event) {
464: for (final Iterator it = _dataTargets.iterator(); it
465: .hasNext();) {
466: final BindingInfo bi = (BindingInfo) it.next();
467: final Component dt = bi.getComponent();
468: final Binding binding = bi.getBinding();
469: final DataBinder binder = binding.getBinder();
470: final Component dataTarget = binder.isTemplate(dt) ? binder
471: .lookupClone(event.getTarget(), dt)
472: : dt;
473: if (dataTarget != null) {
474: binding.loadAttribute(dataTarget);
475: }
476: }
477: }
478: }
479:
480: private static class SaveEventListener extends BaseEventListener {
481: public SaveEventListener() {
482: super ();
483: }
484:
485: public void onEvent(Event event) {
486: final Component target = event.getTarget();
487: final List tmplist = new ArrayList(_dataTargets.size());
488:
489: //fire onSave for each binding
490: for (final Iterator it = _dataTargets.iterator(); it
491: .hasNext();) {
492: final BindingInfo bi = (BindingInfo) it.next();
493: final Component dt = bi.getComponent();
494: final Binding binding = bi.getBinding();
495: final DataBinder binder = binding.getBinder();
496: final Component dataTarget = binder.isTemplate(dt) ? binder
497: .lookupClone(event.getTarget(), dt)
498: : dt;
499: final Object[] vals = binding
500: .getAttributeValues(dataTarget);
501: if (dataTarget != null) {
502: tmplist.add(new BindingInfo(binding, dataTarget,
503: vals));
504: Events.sendEvent(new BindingSaveEvent(
505: "onBindingSave", dataTarget, target,
506: binding, vals[0]));
507: }
508: }
509:
510: //fire onValidate for target component
511: Events.sendEvent(new Event("onBindingValidate", target));
512:
513: //saveAttribute for each binding
514: Component dataTarget = null;
515: final List loadOnSaveInfos = new ArrayList(tmplist.size());
516: for (final Iterator it = tmplist.iterator(); it.hasNext();) {
517: final BindingInfo bi = (BindingInfo) it.next();
518: dataTarget = bi.getComponent();
519: final Binding binding = bi.getBinding();
520: final Object[] vals = bi.getAttributeValues();
521: final DataBinder binder = binding.getBinder();
522: binding.saveAttributeValue(dataTarget, vals,
523: loadOnSaveInfos);
524: }
525:
526: //do loadOnSave (use last dataTarget as proxy
527: if (dataTarget != null) {
528: Events.postEvent(new Event("onLoadOnSave", dataTarget,
529: loadOnSaveInfos));
530: }
531: }
532: }
533:
534: }
|