001: /* BranchInput.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Aug 9, 2007 12:42:46 PM 2007, Created by Dennis.Chen
010: }}IS_NOTE
011:
012: Some code of this file is refer to Apache License Version 2.0
013: the license file is http://www.apache.org/licenses/LICENSE-2.0
014:
015: Copyright (C) 2007 Potix Corporation. All Rights Reserved.
016:
017: {{IS_RIGHT
018: This program is distributed under GPL Version 2.0 in the hope that
019: it will be useful, but WITHOUT ANY WARRANTY.
020: }}IS_RIGHT
021: */
022: package org.zkoss.jsf.zul.impl;
023:
024: import java.util.ArrayList;
025: import java.util.List;
026: import java.util.Map;
027:
028: import javax.faces.FacesException;
029: import javax.faces.application.FacesMessage;
030: import javax.faces.component.EditableValueHolder;
031: import javax.faces.component.UIComponent;
032: import javax.faces.context.FacesContext;
033: import javax.faces.convert.Converter;
034: import javax.faces.convert.ConverterException;
035: import javax.faces.el.EvaluationException;
036: import javax.faces.el.MethodBinding;
037: import javax.faces.el.ValueBinding;
038: import javax.faces.event.AbortProcessingException;
039: import javax.faces.event.FacesEvent;
040: import javax.faces.event.ValueChangeEvent;
041: import javax.faces.event.ValueChangeListener;
042: import javax.faces.render.Renderer;
043: import javax.faces.validator.Validator;
044: import javax.faces.validator.ValidatorException;
045:
046: import org.zkoss.lang.reflect.Fields;
047: import org.zkoss.util.logging.Log;
048: import org.zkoss.zk.ui.Component;
049:
050: /**
051: *
052: * The skeletal class used to implement the ZULJSF components which needs to
053: * support {@link javax.faces.component.EditableValueHolder}.<br/> Components
054: * should be declared nested under {@link org.zkoss.jsf.zul.Page}.
055: *
056: * see Javadoc of <a
057: * href="http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/index.html">JSF
058: * Specification</a>
059: *
060: * @author Dennis.Chen
061: *
062: */
063: abstract public class BranchInput extends BranchOutput implements
064: EditableValueHolder, ClientInputSupport {
065: private static final Log log = Log.lookup(RootComponent.class);
066:
067: // ------------code refer to Apache MyFaces-----------------
068:
069: private Object _submittedValue = null;
070:
071: private Boolean _required;
072:
073: private Boolean _immediate;
074:
075: private boolean _valid = true;
076:
077: private MethodBinding _validator = null;
078:
079: private MethodBinding _valueChangeListener = null;
080:
081: List _validatorList = null;
082:
083: private static final Validator[] EMPTY_VALIDATOR_ARRAY = new Validator[0];
084:
085: public Object getSubmittedValue() {
086: return this ._submittedValue;
087: }
088:
089: public void setSubmittedValue(Object submittedValue) {
090: this ._submittedValue = submittedValue;
091: }
092:
093: public boolean isRequired() {
094: if (_required != null) {
095: return (_required.booleanValue());
096: }
097: ValueBinding vb = getValueBinding("required");
098: if (vb != null) {
099: return Boolean.TRUE.equals(vb.getValue(getFacesContext()));
100: } else {
101: return false;
102: }
103: }
104:
105: public boolean isValid() {
106: return this ._valid;
107: }
108:
109: public void setValid(boolean valid) {
110: this ._valid = valid;
111: }
112:
113: public void setRequired(boolean required) {
114: this ._required = required ? Boolean.TRUE : Boolean.FALSE;
115: }
116:
117: public boolean isImmediate() {
118: if (_immediate != null) {
119: return (_immediate.booleanValue());
120: }
121: ValueBinding vb = getValueBinding("immediate");
122: if (vb != null) {
123: return (Boolean.TRUE.equals(vb.getValue(getFacesContext())));
124: } else {
125: return false;
126: }
127: }
128:
129: public void setImmediate(boolean immediate) {
130: _immediate = immediate ? Boolean.TRUE : Boolean.FALSE;
131: }
132:
133: public MethodBinding getValidator() {
134: return this ._validator;
135: }
136:
137: public void setValidator(MethodBinding validatorBinding) {
138: this ._validator = validatorBinding;
139: }
140:
141: public MethodBinding getValueChangeListener() {
142: return (this ._valueChangeListener);
143: }
144:
145: public void setValueChangeListener(MethodBinding valueChangeMethod) {
146: this ._valueChangeListener = valueChangeMethod;
147: }
148:
149: /**
150: * Set the "submitted value" of this component from the relevant data in the
151: * current servlet request object.
152: * <p>
153: * If this component is not rendered, then do nothing; no output would have
154: * been sent to the client so no input is expected.
155: * <p>
156: * Invoke the inherited functionality, which typically invokes the renderer
157: * associated with this component to extract and set this component's
158: * "submitted value".
159: * <p>
160: * If this component is marked "immediate", then immediately apply
161: * validation to the submitted value found. On error, call context method
162: * "renderResponse" which will force processing to leap to the "render
163: * response" phase as soon as the "decode" step has completed for all other
164: * components.
165: */
166: public void processDecodes(FacesContext context) {
167: if (context == null) {
168: throw new NullPointerException("context");
169: }
170:
171: // Skip processing if our rendered flag is false
172: if (!isRendered()) {
173: return;
174: }
175:
176: super .processDecodes(context);
177:
178: if (isImmediate()) {
179: try {
180: validate(context);
181: } catch (RuntimeException e) {
182: context.renderResponse();
183: throw e;
184: }
185:
186: if (!isValid()) {
187: context.renderResponse();
188: }
189: }
190: }
191:
192: /**
193: * <p>
194: * In addition to the standard <code>processValidators</code> behavior
195: * inherited from {@link javax.faces.component.UIComponentBase}, calls
196: * <code>validate()</code> if the <code>immediate</code> property is
197: * false (which is the default); if the component is invalid afterwards,
198: * calls {@link javax.faces.context.FacesContext#renderResponse}. If a
199: * <code>RuntimeException</code> is thrown during validation processing,
200: * calls {@link javax.faces.context.FacesContext#renderResponse} and
201: * re-throw the exception.
202: * </p>
203: *
204: * @exception NullPointerException
205: */
206: public void processValidators(FacesContext context) {
207:
208: if (context == null) {
209: throw new NullPointerException();
210: }
211:
212: // Skip processing if our rendered flag is false
213: if (!isRendered()) {
214: return;
215: }
216:
217: super .processValidators(context);
218: if (!isImmediate()) {
219: try {
220: validate(context);
221: } catch (RuntimeException e) {
222: context.renderResponse();
223: throw e;
224: }
225:
226: if (!isValid()) {
227: context.renderResponse();
228: }
229: }
230: }
231:
232: public void processUpdates(FacesContext context) {
233:
234: if (context == null) {
235: throw new NullPointerException("context");
236: }
237:
238: // Skip processing if our rendered flag is false
239: if (!isRendered()) {
240: return;
241: }
242:
243: super .processUpdates(context);
244:
245: try {
246: updateModel(context);
247: } catch (RuntimeException e) {
248: context.renderResponse();
249: throw e;
250: }
251:
252: if (!isValid()) {
253: context.renderResponse();
254: }
255: }
256:
257: /**
258: * @exception NullPointerException
259: */
260: public void decode(FacesContext context) {
261:
262: if (context == null) {
263: throw new NullPointerException();
264: }
265:
266: // We (re)set to valid, so that component automatically gets
267: // (re)validated
268: setValid(true);
269: super .decode(context);
270:
271: // call abstract method to decode input , add by dennis.
272: clientInputDecode(context);
273: }
274:
275: public void broadcast(FacesEvent event)
276: throws AbortProcessingException {
277:
278: // why MyFaces check Event type here? should it never wrong?, remove by dennis...
279: /*
280: * if (!(event instanceof ValueChangeEvent)) { throw new
281: * IllegalArgumentException("FacesEvent of class " +
282: * event.getClass().getName() + " not supported by UIInput"); }
283: */
284:
285: // invoke standard listeners attached to this component first
286: super .broadcast(event);
287:
288: // check is ValueChangeEvent
289: if (event instanceof ValueChangeEvent) {
290:
291: MethodBinding valueChangeListenerBinding = getValueChangeListener();
292: if (valueChangeListenerBinding != null) {
293: FacesContext context = getFacesContext();
294: try {
295: valueChangeListenerBinding.invoke(context,
296: new Object[] { event });
297: } catch (EvaluationException e) {
298: Throwable cause = e.getCause();
299: if (cause != null
300: && cause instanceof AbortProcessingException) {
301: throw (AbortProcessingException) cause;
302: } else {
303: throw e;
304: }
305: }
306: }
307: }
308:
309: }
310:
311: public void updateModel(FacesContext context) {
312:
313: if (context == null) {
314: throw new NullPointerException("content");
315: }
316:
317: if (!isValid())
318: return;
319: if (!isLocalValueSet())
320: return;
321:
322: ValueBinding vb = getValueBinding("value");
323: if (vb == null)
324: return;
325: try {
326: vb.setValue(context, getLocalValue());
327: setValue(null);
328: setLocalValueSet(false);
329: } catch (EvaluationException ee) {
330: Throwable cause = ee.getCause();
331: if (cause != null) {
332: log
333: .warning(
334: "exception when set value("
335: + getLocalValue()
336: + ") to Bean , please check EL and Object type. ",
337: ee);
338: }
339:
340: String exceptionMessage = ee.getMessage();
341: if (exceptionMessage == null) {
342: addErrorMessage(context, this ,
343: MessageFactory.CONVERSION_MESSAGE_ID,
344: new Object[] { getId() });
345: } else {
346: addErrorMessage(context, this , exceptionMessage,
347: new Object[] { getId() });
348: }
349:
350: setValid(false);
351: } catch (Exception e) {
352: log.warning(e);
353: context.getExternalContext().log(e.getMessage(), e);
354: addErrorMessage(context, this ,
355: MessageFactory.CONVERSION_MESSAGE_ID,
356: new Object[] { getId() });
357: setValid(false);
358: }
359: }
360:
361: protected void validateValue(FacesContext context,
362: Object convertedValue) {
363: boolean empty = convertedValue == null
364: || (convertedValue instanceof String && ((String) convertedValue)
365: .length() == 0);
366:
367: if (isRequired() && empty) {
368: addErrorMessage(context, this ,
369: MessageFactory.REQUIRED_MESSAGE_ID,
370: new Object[] { getId() });
371: setValid(false);
372: return;
373: }
374:
375: if (!empty) {
376: callValidators(context, this , convertedValue);
377: }
378: }
379:
380: /**
381: * Determine whether the new value is valid, and queue a ValueChangeEvent if
382: * necessary.
383: * <p>
384: * The "submitted value" is converted to the necessary type; conversion
385: * failure is reported as an error and validation processing terminates for
386: * this component. See documentation for method getConvertedValue for
387: * details on the conversion process.
388: * <p>
389: * Any validators attached to this component are then run, passing the
390: * converted value.
391: * <p>
392: * The old value of this component is then fetched (possibly involving the
393: * evaluation of a value-binding expression, ie invoking a method on a user
394: * object). The old value is compared to the new validated value, and if
395: * they are different then a ValueChangeEvent is queued for later
396: * processing.
397: * <p>
398: * On successful completion of this method:
399: * <ul>
400: * <li> isValid() is true
401: * <li> isLocalValueSet() is true
402: * <li> submittedValue is reset to null
403: * <li> a ValueChangeEvent is queued if the new value != old value
404: * </ul>
405: */
406: public void validate(FacesContext context) {
407:
408: if (context == null)
409: throw new NullPointerException("context");
410: Object submittedValue = getSubmittedValue();
411: if (submittedValue == null)
412: return;
413:
414: Object convertedValue = getConvertedValue(context,
415: submittedValue);
416:
417: if (!isValid())
418: return;
419:
420: validateValue(context, convertedValue);
421:
422: if (!isValid())
423: return;
424:
425: Object previousValue = getValue();
426: setValue(convertedValue);
427: setSubmittedValue(null);
428: if (compareValues(previousValue, convertedValue)) {
429: queueEvent(new ValueChangeEvent(this , previousValue,
430: convertedValue));
431: }
432:
433: }
434:
435: /**
436: * Convert the provided object to the desired value.
437: * <p>
438: * If there is a renderer for this component, then call the renderer's
439: * getConvertedValue method. While this can of course be implemented in any
440: * way the renderer desires, it typically performs exactly the same
441: * processing that this method would have done anyway (ie that described
442: * below for the no-renderer case).
443: * <p>
444: * Otherwise:
445: * <ul>
446: * <li>If the submittedValue is not a String then just return the
447: * submittedValue unconverted.
448: * <li>If there is no "value" value-binding then just return the
449: * submittedValue unconverted.
450: * <li>Use introspection to determine the type of the target property
451: * specified by the value-binding, and then use Application.createConverter
452: * to find a converter that can map from String to the required type. Apply
453: * the converter to the submittedValue and return the result.
454: * </ul>
455: */
456: protected Object getConvertedValue(FacesContext context,
457: Object submittedValue) {
458: try {
459: Renderer renderer = getRenderer(context);
460: if (renderer != null) {
461: return renderer.getConvertedValue(context, this ,
462: submittedValue);
463: } else if (submittedValue instanceof String) {
464: Converter converter = findBranchInputConverter(context,
465: this );
466: if (converter != null) {
467: return converter.getAsObject(context, this ,
468: (String) submittedValue);
469: }
470: }
471: } catch (ConverterException e) {
472: FacesMessage facesMessage = e.getFacesMessage();
473: if (facesMessage != null) {
474: context.addMessage(getClientId(context), facesMessage);
475: } else {
476: addErrorMessage(context, this ,
477: MessageFactory.CONVERSION_MESSAGE_ID,
478: new Object[] { getId() });
479: }
480: setValid(false);
481: }
482: return submittedValue;
483: }
484:
485: protected boolean compareValues(Object previous, Object value) {
486: return previous == null ? (value != null) : (!previous
487: .equals(value));
488: }
489:
490: public void addValidator(Validator validator) {
491:
492: if (validator == null)
493: throw new NullPointerException("validator");
494: if (_validatorList == null) {
495: _validatorList = new ArrayList();
496: }
497: _validatorList.add(validator);
498:
499: }
500:
501: public Validator[] getValidators() {
502: return _validatorList != null ? (Validator[]) _validatorList
503: .toArray(new Validator[_validatorList.size()])
504: : EMPTY_VALIDATOR_ARRAY;
505: }
506:
507: public void removeValidator(Validator validator) {
508: if (validator == null)
509: throw new NullPointerException("validator");
510: if (_validatorList != null) {
511: _validatorList.remove(validator);
512: }
513: }
514:
515: public void addValueChangeListener(ValueChangeListener listener) {
516: addFacesListener(listener);
517: }
518:
519: public ValueChangeListener[] getValueChangeListeners() {
520: return (ValueChangeListener[]) getFacesListeners(ValueChangeListener.class);
521: }
522:
523: public void removeValueChangeListener(ValueChangeListener listener) {
524: removeFacesListener(listener);
525: }
526:
527: /**
528: * Override Method, save the state of this component.
529: */
530: public Object saveState(FacesContext context) {
531:
532: Object values[] = new Object[7];
533: values[0] = super .saveState(context);
534: values[1] = _immediate;
535: values[2] = _required;
536: values[3] = this ._valid ? Boolean.TRUE : Boolean.FALSE;
537: values[4] = saveAttachedState(context, _validator);
538: values[5] = saveAttachedState(context, _valueChangeListener);
539: values[6] = saveAttachedState(context, _validatorList);
540: return values;
541:
542: }
543:
544: /**
545: * Override Method, restore the state of this component.
546: */
547: public void restoreState(FacesContext context, Object state) {
548:
549: Object values[] = (Object[]) state;
550: super .restoreState(context, values[0]);
551: _immediate = ((Boolean) values[1]);
552: _required = ((Boolean) values[2]);
553: _valid = ((Boolean) values[3]).booleanValue();
554: _validator = (MethodBinding) restoreAttachedState(context,
555: values[4]);
556: _valueChangeListener = (MethodBinding) restoreAttachedState(
557: context, values[5]);
558: _validatorList = (List) restoreAttachedState(context, values[6]);
559:
560: }
561:
562: // -------------end of code refer ----------------
563:
564: // --------------ZUL JSF implemenation ----------------
565:
566: /**
567: * Override Method, if instance implements {@link ClientInputSupport}
568: * (for now, it is always ), then i will try to set the name/value which get from
569: * ClientInputSupport into zulcomp. For Example of TextboxComponent, a
570: * name=form:compid will set to zulcomp and the result in HTML will likes
571: * (<input id="z_fm_2c" type="text"
572: * name="helloForm:txt1" value="0"
573: * z.type="zul.widget.Txbox"/> ). And then , after form submit,
574: * I can decode the submitted value from request by name "helloForm:txt1".
575: *
576: */
577: protected void afterZULComponentComposed(Component zulcomp) {
578: super .afterZULComponentComposed(zulcomp);
579: if (this instanceof ClientInputSupport) {
580: String att = ((ClientInputSupport) this )
581: .getInputAttributeName();
582: String value = ((ClientInputSupport) this )
583: .getInputAttributeValue();
584: try {
585: Fields.setField(zulcomp, att, value, true);
586: } catch (Exception x) {
587: throw new RuntimeException(x.getMessage(), x);
588: }
589: }
590: }
591:
592: // --------------Client Input Interface----------
593:
594: /**
595: * Return ZUL Component attribute name which can handler the submition of
596: * input.
597: *
598: * Note : Default is "name"
599: *
600: * @see ClientInputSupport
601: */
602: public String getInputAttributeName() {
603: return "name";
604: }
605:
606: /**
607: * Return ZUL Component attribute value which can handler the submition of
608: * input.
609: *
610: * @see ClientInputSupport
611: * @see javax.faces.component.UIComponent#getClientId(FacesContext)
612: */
613: public String getInputAttributeValue() {
614: String cid = super .getClientId(getFacesContext());
615: return cid;
616: }
617:
618: /**
619: * Decode value in request's parameter. call by {@link #decode}
620: */
621: protected void clientInputDecode(FacesContext context) {
622: String clientId = this .getClientId(context);
623: Map requestMap = context.getExternalContext()
624: .getRequestParameterMap();
625: if (requestMap.containsKey(clientId)) {
626: String newValue = (String) context.getExternalContext()
627: .getRequestParameterMap().get(clientId);
628: setSubmittedValue(newValue);
629: }
630: }
631:
632: protected void addErrorMessage(FacesContext context,
633: UIComponent component, String messageId, Object[] parms) {
634: FacesMessage message = MessageFactory.getMessage(context,
635: messageId);
636: message.setSeverity(FacesMessage.SEVERITY_ERROR);
637: context.addMessage(component.getClientId(context), message);
638: }
639:
640: private static void callValidators(FacesContext context,
641: BranchInput input, Object convertedValue) {
642: // first invoke the list of validator components
643: Validator[] validators = input.getValidators();
644: for (int i = 0; i < validators.length; i++) {
645: Validator validator = validators[i];
646: try {
647: validator.validate(context, input, convertedValue);
648: } catch (ValidatorException e) {
649: input.setValid(false);
650: FacesMessage facesMessage = e.getFacesMessage();
651: if (facesMessage != null) {
652: facesMessage
653: .setSeverity(FacesMessage.SEVERITY_ERROR);
654: context.addMessage(input.getClientId(context),
655: facesMessage);
656: }
657: }
658: }
659:
660: // now invoke the validator method defined as a method-binding attribute
661: // on the component
662: MethodBinding validatorBinding = input.getValidator();
663: if (validatorBinding != null) {
664: try {
665: validatorBinding.invoke(context, new Object[] {
666: context, input, convertedValue });
667: } catch (EvaluationException e) {
668: input.setValid(false);
669: Throwable cause = e.getCause();
670: if (cause instanceof ValidatorException) {
671: FacesMessage facesMessage = ((ValidatorException) cause)
672: .getFacesMessage();
673: if (facesMessage != null) {
674: facesMessage
675: .setSeverity(FacesMessage.SEVERITY_ERROR);
676: context.addMessage(input.getClientId(context),
677: facesMessage);
678: }
679: } else {
680: throw e;
681: }
682: }
683: }
684: }
685:
686: private static Converter findBranchInputConverter(
687: FacesContext facesContext, BranchInput component) {
688: // Attention!
689: // This code is duplicated in myfaces implementation renderkit package.
690: // If you change something here please do the same in the other class!
691:
692: Converter converter = component.getConverter();
693: if (converter != null)
694: return converter;
695:
696: // Try to find out by value binding
697: ValueBinding vb = component.getValueBinding("value");
698: if (vb == null)
699: return null;
700:
701: Class valueType = vb.getType(facesContext);
702: if (valueType == null)
703: return null;
704:
705: if (String.class.equals(valueType))
706: return null; // No converter needed for String type
707: if (Object.class.equals(valueType))
708: return null; // There is no converter for Object class
709:
710: try {
711: return facesContext.getApplication().createConverter(
712: valueType);
713: } catch (FacesException e) {
714: log.debug("No Converter for type " + valueType.getName()
715: + " found", e);
716: return null;
717: }
718: }
719:
720: }
|