/* SavePropertyBindingImpl.java Purpose: Description: History: Aug 1, 2011 2:45:43 PM, Created by henrichen Copyright (C) 2011 Potix Corporation. All Rights Reserved. */ package org.zkoss.bind.impl; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Map; import org.zkoss.bind.BindContext; import org.zkoss.bind.Binder; import org.zkoss.bind.Converter; import org.zkoss.bind.Property; import org.zkoss.bind.ValidationContext; import org.zkoss.bind.Validator; import org.zkoss.bind.proxy.FormProxyObject; import org.zkoss.bind.sys.BindEvaluatorX; import org.zkoss.bind.sys.BinderCtrl; import org.zkoss.bind.sys.ConditionType; import org.zkoss.bind.sys.SavePropertyBinding; import org.zkoss.bind.sys.debugger.BindingExecutionInfoCollector; import org.zkoss.bind.sys.debugger.impl.info.SaveInfo; import org.zkoss.bind.sys.debugger.impl.info.ValidationInfo; import org.zkoss.xel.ExpressionX; import org.zkoss.xel.ValueReference; import org.zkoss.zel.PropertyNotFoundException; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.UiException; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.WrongValuesException; /** * Implementation of {@link SavePropertyBinding}. * @author henrichen * @since 6.0.0 */ public class SavePropertyBindingImpl extends PropertyBindingImpl implements SavePropertyBinding { private static final long serialVersionUID = 1463169907348730644L; private final ExpressionX _validator; private final Map<String, Object> _validatorArgs; private FormFieldInfo _formFieldInfo; public SavePropertyBindingImpl(Binder binder, Component comp, String attr, String saveAttr, String saveExpr, ConditionType conditionType, String command, Map<String, Object> bindingArgs, String converterExpr, Map<String, Object> converterArgs, String validatorExpr, Map<String, Object> validatorArgs) { super(binder, comp, attr, "self." + saveAttr, saveExpr, conditionType, command, bindingArgs, converterExpr, converterArgs); final BindEvaluatorX eval = binder.getEvaluatorX(); _validator = validatorExpr == null ? null : parseValidator(eval, validatorExpr); _validatorArgs = validatorArgs; //ZK-3185: Enable form validation with reference and collection binding final BindContext ctx = BindContextUtil.newBindContext(getBinder(), this, true, null, getComponent(), null); ctx.setAttribute(BinderImpl.IGNORE_REF_VALUE, Boolean.FALSE); ctx.setAttribute(BinderImpl.IGNORE_TRACKER, Boolean.TRUE); ValueReference valref = null; try { valref = eval.getValueReference(ctx, getComponent(), _accessInfo.getProperty()); } catch (PropertyNotFoundException e) { //ignore null } if (valref != null) { Object base = valref.getBase(); if (base instanceof FormProxyObject) ((FormProxyObject) base).cacheSavePropertyBinding((String) valref.getProperty(), this); } } public Map<String, Object> getValidatorArgs() { return _validatorArgs; } protected boolean ignoreTracker() { return true; } private ExpressionX parseValidator(BindEvaluatorX eval, String validatorExpr) { // final BindContext ctx = BindContextUtil.newBindContext(getBinder(), this, false, null, getComponent(), null); // ctx.setAttribute(BinderImpl.IGNORE_TRACKER, Boolean.TRUE);//ignore tracker when doing el, we don't need to trace validator //expression will/should not be tracked, (although, from the impl, tracker don't care savebinding) return eval.parseExpressionX(/*ctx*/null, validatorExpr, Object.class); } public Validator getValidator() { if (_validator == null) return null; // final BindContext ctx = BindContextUtil.newBindContext(getBinder(), this, false, null, getComponent(), null); // ctx.setAttribute(BinderImpl.IGNORE_TRACKER, Boolean.TRUE);//ignore tracker when doing el, we don't need to trace validator final BindEvaluatorX eval = getBinder().getEvaluatorX(); Object obj = eval.getValue(null, getComponent(), _validator); if (obj instanceof Validator) { return (Validator) obj; } else if (obj instanceof String) { return getBinder().getValidator((String) obj); //binder will throw exception if not found } else { throw new ClassCastException( "result of expression '" + _validator.getExpressionString() + "' is not a Validator, is " + obj); } } private static final String $COMPVALUE$ = "$COMPVALUE$"; private static final String $COMPVALUENOCONVERT$ = "$COMPVALUENOCONVERT$"; private static final String $VALUEREF$ = "$VALUEREF$"; private Object getComponentValue(BindContext ctx) { if (!containsAttribute(ctx, $COMPVALUE$)) { final Component comp = getComponent(); //ctx.getComponent(); final BindEvaluatorX eval = getBinder().getEvaluatorX(); //get data from component attribute Object value = eval.getValue(null, comp, _fieldExpr); setAttribute(ctx, $COMPVALUENOCONVERT$, value); //use converter to convert type if any @SuppressWarnings("unchecked") final Converter<Object, Object, Component> conv = getConverter(); if (conv != null) { value = conv.coerceToBean(value, comp, ctx); // ValueReference ref = getValueReference(ctx); // //collect Property for @NotifyChange, kept in BindContext // //see BinderImpl$CommandEventListener#onEvent() // BindELContext.addNotifys(getConverterMethod(conv.getClass()), ref.getBase(), null, value, ctx); } setAttribute(ctx, $COMPVALUE$, value); } return getAttribute(ctx, $COMPVALUE$); } public String getValidatorExpressionString() { return _validator == null ? null : BindEvaluatorXUtil.getExpressionString(_validator); } public void save(BindContext ctx) { final BindingExecutionInfoCollector collector = ((BinderCtrl) getBinder()).getBindingExecutionInfoCollector(); final Component comp = getComponent(); //get data from component attribute Object value = getComponentValue(ctx); if (value == Converter.IGNORED_VALUE) { if (collector != null) { Object old = getAttribute(ctx, $COMPVALUENOCONVERT$); collector.addInfo(new SaveInfo(SaveInfo.PROP_SAVE, comp, getConditionString(ctx), getFieldName(), getPropertyString(), old, getArgs(), "*Converter.IGNORED_VALUE")); } return; } if (collector != null) { collector.addInfo(new SaveInfo(SaveInfo.PROP_SAVE, comp, getConditionString(ctx), getFieldName(), getPropertyString(), value, getArgs(), null)); } //set data into bean property final BindEvaluatorX eval = getBinder().getEvaluatorX(); eval.setValue(ctx, comp, _accessInfo.getProperty(), value); } private String getConditionString(BindContext ctx) { StringBuilder condition = new StringBuilder(); if (getConditionType() == ConditionType.BEFORE_COMMAND) { condition.append("before = '").append(getCommandName()).append("'"); } else if (getConditionType() == ConditionType.AFTER_COMMAND) { condition.append("after = '").append(getCommandName()).append("'"); } else { condition.append(ctx.getTriggerEvent() == null ? "" : "event = " + ctx.getTriggerEvent().getName()); } return condition.toString(); } //get and cache value reference of this binding private ValueReference getValueReference(BindContext ctx) { ValueReference valref = (ValueReference) getAttribute(ctx, $VALUEREF$); if (valref == null) { //ZK-1017: Property of a form is not correct when validation if (_formFieldInfo != null && _formFieldInfo._fieldName.indexOf("[$INDEX$]") == -1) { // skip collection field name final Object form = getComponent().getAttribute(_formFieldInfo._id, true); final String fieldName = _formFieldInfo._fieldName; valref = new org.zkoss.xel.zel.ELXelExpression.ValueReferenceImpl(form, fieldName); } else { final Component comp = getComponent(); //ctx.getComponent(); final BindEvaluatorX eval = getBinder().getEvaluatorX(); valref = eval.getValueReference(ctx, comp, _accessInfo.getProperty()); if (valref == null) { throw new UiException("value reference not found by expression [" + _accessInfo.getProperty().getExpressionString() + "], check if you are trying to save to a variable only expression"); } } setAttribute(ctx, $VALUEREF$, valref); } return valref; } //ZK-1017: Property of a form is not correct when validation //@see BinderImpl#addFormAssociatedSaveBinding /*package*/ void setFormFieldInfo(Component formComp, String formId, String fieldName) { if (_formFieldInfo == null) { _formFieldInfo = new FormFieldInfo(); } _formFieldInfo._component = formComp; _formFieldInfo._id = formId; _formFieldInfo._fieldName = fieldName; } //--SaveBinding--// public Property getValidate(BindContext ctx) { //we should not check this binding need to validate or not, //maybe other validator want to know the value of this binding, so just provide it final ValueReference ref = getValueReference(ctx); try { final Object value = getComponentValue(ctx); return new PropertyImpl(ref.getBase(), String.valueOf(ref.getProperty()), value == Converter.IGNORED_VALUE ? null : value); } catch (Exception e) { // ZK-878 Exception if binding a form with errorMessage // a wrong value exception might be thrown when a component has constraint Throwable t = e; while (t != null) { if (t instanceof WrongValueException || t instanceof WrongValuesException) { return new WrongValuePropertyImpl(ref.getBase(), (String) ref.getProperty(), t); } else { t = t.getCause(); } } throw UiException.Aide.wrap(e); } } public boolean hasValidator() { return _validator == null ? false : true; } public void validate(ValidationContext vctx) { Validator validator = getValidator(); if (validator == null) { throw new NullPointerException("cannot find validator for " + this); } //ZK-1005 ZK 6.0.1 validation fails on nested bean if (_formFieldInfo != null) { vctx.getBindContext().setAttribute(BinderImpl.LOAD_FORM_COMPONENT, _formFieldInfo._component); } validator.validate(vctx); BindingExecutionInfoCollector collector = ((BinderCtrl) getBinder()).getBindingExecutionInfoCollector(); if (collector != null) { collector.addInfo(new ValidationInfo(ValidationInfo.PROP, getComponent(), getValidatorExpressionString(), validator.toString(), Boolean.valueOf(vctx.isValid()), ((BindContextImpl) vctx.getBindContext()).getValidatorArgs(), null)); } // //collect notify change // collectNotifyChange(validator,vctx); } // private void collectNotifyChange(Validator validator, ValidationContext vctx) { // ValueReference ref = getValueReference(vctx.getBindContext()); // BindELContext.addNotifys(getValidatorMethod(validator.getClass()), ref.getBase(), null, null, vctx.getBindContext()); // } private Method getConverterMethod(Class<? extends Converter> cls) { try { return cls.getMethod("coerceToBean", new Class[] { Object.class, Component.class, BindContext.class }); } catch (NoSuchMethodException e) { //ignore } return null; //shall never come here } private Method getValidatorMethod(Class<? extends Validator> cls) { try { return cls.getMethod("validate", new Class[] { ValidationContext.class }); } catch (NoSuchMethodException e) { //ignore } return null; //shall never come here } //ZK-1005 ZK 6.0.1 validation fails on nested bean private static class FormFieldInfo implements Serializable { private static final long serialVersionUID = 1L; private Component _component; private String _fieldName; private String _id; } /** * Internal use only, only for collections */ public Property getBasePropertyIfFromCollection() { Property p = null; int index = -1; String fieldName = ""; if (_formFieldInfo != null && (fieldName = _formFieldInfo._fieldName) != null && (index = fieldName.indexOf("[$INDEX$]")) != -1) { final BindContext ctx = BindContextUtil.newBindContext(getBinder(), this, false, null, getComponent(), null); final ValueReference valref = getValueReference(ctx); if (valref != null) { Object base = valref.getBase(); if (base instanceof FormProxyObject) base = ((FormProxyObject) base).getOriginObject(); if (base != null) { final BindEvaluatorX eval = getBinder().getEvaluatorX(); Object value = eval.getValue(ctx, getComponent(), _accessInfo.getProperty()); String replacedFieldString = fieldName.substring(0, index) + ".$each"; if (fieldName.length() > (index + 9)) replacedFieldString += fieldName.substring(index + 9); p = new PropertyImpl(base, replacedFieldString, value); } } } return p; } }