package com.aggrepoint.winlet.form;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.support.RequestContextUtils;
import com.aggrepoint.winlet.ContextUtils;
import com.aggrepoint.winlet.ReqConst;
import com.aggrepoint.winlet.ReqInfo;
import com.aggrepoint.winlet.spring.WinletDefaultFormattingConversionService;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class FormImpl implements Form, ReqConst {
static final String FORM_DATA_SESSION_KEY = FormImpl.class.getName();
private ArrayList<WebDataBinder> binders = new ArrayList<WebDataBinder>();
private ReqInfo ri;
private ArrayList<String> fields = new ArrayList<String>();
private HashSet<String> groupNames = new HashSet<String>();
private Hashtable<String, Vector<String>> fieldValues = new Hashtable<String, Vector<String>>();
private Hashtable<String, ArrayList<String>> fieldErrors = new Hashtable<String, ArrayList<String>>();
/** bindingErrorCount的值为在全表单校验时BindingError的计数,不管这些Error是否有对应Form中的Field。 */
/** 通过程序代码提交表单请求时,请求中可能不带表单字段名称列表,即使数据有错误,fieldErrors也可能为空。 */
/** 一般情况下要确保bindingErrorCount也不为0才可以视为表单数据校验通过。 */
private int bindingErrorCount;
private HashSet<String> disabledFields;
private Vector<Change> vecChanges = new Vector<Change>();
static Pattern ARRAY = Pattern.compile("^(.+)\\[(\\d+)\\]$");
public FormImpl(ReqInfo ri) {
this.ri = ri;
if (ri.isValidateField()) {
fields.add(ri.getValidateFieldName());
disabledFields = new HashSet<String>();
} else {
toCollection(fields,
ri.getRequest().getParameterValues(PARAM_WIN_FORM_FIELDS));
disabledFields = toSet(ri.getRequest().getParameterValues(
PARAM_WIN_FORM_DISABLED_FIELD));
}
for (String field : fields) {
Matcher m = ARRAY.matcher(field);
if (m.find())
groupNames.add(m.group(1));
}
}
public boolean isValidateField() {
return ri.isValidateField();
}
public String getValidateFieldName() {
if (ri.isValidateField())
return ri.getValidateFieldName();
return null;
}
public String getValidateFieldId() {
if (ri.isValidateField())
return ri.getValidateFieldId();
return null;
}
public boolean isValidateForm() {
String val = ri.getParameter(PARAM_WIN_FORM_VALIDATE, "no");
return "yes".equalsIgnoreCase(val) || "form".equals(val)
|| "field".equals(val);
}
public boolean validate(String field) {
if (disabledFields != null && disabledFields.contains(field))
return false;
return !ri.isValidateField() // 不是单字段校验,对所有字段都校验
|| fields.contains(field) || groupNames.contains(field);
}
public void recordChange(Change change) {
change.addTo(vecChanges);
}
public void removeChange(Change change) {
vecChanges
.removeAll(Change.find(vecChanges, change.type, change.input));
}
private static HashSet<String> toSet(String[] vals) {
HashSet<String> set = new HashSet<String>();
toCollection(set, vals);
return set;
}
private static void toCollection(Collection<String> c, String[] vals) {
if (vals != null)
for (String s : vals)
if (!c.contains(s))
c.add(s);
}
@Override
public String getValue(String field, String def) {
processBinders();
String val = getValue(field);
return val == null ? def : val;
}
@Override
public String getValue(String field) {
processBinders();
String[] val = getValues(field);
if (val == null || val.length < 1)
return null;
return val[0];
}
@Override
public String[] getValues(String field) {
processBinders();
String name = field;
Integer idx = null;
Matcher m = ARRAY.matcher(field);
if (m.find()) {
name = m.group(1);
idx = Integer.parseInt(m.group(2));
}
Vector<String> vals = fieldValues.get(name);
if (vals != null) {
if (idx == null)
return vals.toArray(new String[vals.size()]);
if (vals.size() > idx)
return new String[] { vals.get(idx) };
}
if (ri.isValidateField()) {
if (field.equals(ri.getValidateFieldName()))
return new String[] { ri.getValidateFieldValue() };
} else {
String[] val = ri.getRequest().getParameterValues(name);
if (val == null)
return null;
if (idx == null)
return val;
if (val.length > idx)
return new String[] { val[idx] };
}
return null;
}
@Override
public void setValue(String field, String value) {
processBinders();
String name = field;
Integer idx = null;
Matcher m = ARRAY.matcher(field);
if (m.find()) {
name = m.group(1);
idx = Integer.parseInt(m.group(2));
}
Vector<String> values = fieldValues.get(name);
if (values == null) {
values = new Vector<String>();
fieldValues.put(name, values);
}
if (idx == null) {
values.clear();
values.add(value);
} else {
for (int i = values.size(); i <= idx; i++) {
values.add("");
}
values.set(idx, value);
}
recordChange(new ChangeUpdateValue(field, value));
}
@Override
public void setValue(String field, String[] value) {
processBinders();
Vector<String> values = fieldValues.get(field);
if (values == null) {
values = new Vector<String>();
fieldValues.put(field, values);
}
values.clear();
if (value != null)
for (String v : value)
values.add(v);
recordChange(new ChangeUpdateValue(field, value));
}
@Override
public void setSelectOptions(String field,
Collection<? extends SelectOption> list) {
processBinders();
recordChange(new ChangeUpdateList(field, list));
}
@Override
public boolean hasError() {
processBinders();
return fieldErrors.size() > 0 || bindingErrorCount > 0;
}
@Override
public boolean hasErrorOrValidateField() {
return hasError() || isValidateField();
}
@Override
public boolean hasErrorOrValidateField(boolean fieldErrorsOnly) {
return hasError(fieldErrorsOnly) || isValidateField();
}
@Override
public boolean hasError(boolean fieldErrorsOnly) {
processBinders();
if (fieldErrorsOnly)
return fieldErrors.size() > 0;
return fieldErrors.size() > 0 || bindingErrorCount > 0;
}
@Override
public boolean hasError(String field) {
processBinders();
ArrayList<String> errors = fieldErrors.get(field);
if (errors == null || errors.size() == 0)
return false;
return true;
}
@Override
public String[] getErrors(String field) {
processBinders();
ArrayList<String> errors = fieldErrors.get(field);
if (errors == null || errors.size() == 0)
return null;
return errors.toArray(new String[errors.size()]);
}
@Override
public void addError(String field, String error) {
processBinders();
ArrayList<String> errors = fieldErrors.get(field);
if (errors == null) {
errors = new ArrayList<String>();
fieldErrors.put(field, errors);
}
errors.add(error);
recordChange(new ChangeUpdateValidateResult(field, error));
}
@Override
public void clearErrors() {
processBinders();
fieldErrors.clear();
vecChanges.clear();
}
@Override
public void clearError(String field) {
processBinders();
fieldErrors.remove(field);
removeChange(new ChangeUpdateValidateResult(field, ""));
}
@Override
public void setDisabled(String field) {
processBinders();
disabledFields.add(field);
clearError(field);
recordChange(new ChangeDisable(field));
}
@Override
public void setEnabled(String field) {
processBinders();
disabledFields.remove(field);
recordChange(new ChangeEnable(field));
}
@Override
public void show(String selector) {
processBinders();
recordChange(new ChangeShow(selector));
}
@Override
public void hide(String selector) {
processBinders();
recordChange(new ChangeHide(selector));
}
@Override
public String[] getDisabledFields() {
processBinders();
return disabledFields.toArray(new String[disabledFields.size()]);
}
private static Hashtable<Method, ArrayList<FormValidator>> HT_VALIDATORS = new Hashtable<Method, ArrayList<FormValidator>>();
private ArrayList<FormValidator> getValidators(Object controller, Method m) {
ArrayList<FormValidator> validators = HT_VALIDATORS.get(m);
if (validators == null) {
validators = new ArrayList<FormValidator>();
HT_VALIDATORS.put(m, validators);
Validate[] ann = m.getAnnotationsByType(Validate.class);
ArrayList<Validate> vs = new ArrayList<Validate>();
if (ann != null) {
for (Validate vl : ann)
if (!vl.after())
vs.add(vl);
for (Validate vl : ann)
if (vl.after())
vs.add(vl);
for (Validate vl : vs)
validators.add(new FormValidator(controller, vl.name(), vl
.pattern(), vl.id(), vl.method(), vl.passskip()
.name(), vl.failskip().name(), vl.error(),
new Vector<String>(Arrays.asList(vl.args()))));
}
}
return validators;
}
public void validate(ReqInfo ri, Object controller, Method method) {
processBinders();
ArrayList<FormValidator> vs = getValidators(controller, method);
for (String field : fields) {
if (disabledFields.contains(field))
continue;
String[] values = this.getValues(field);
String value = values == null || values.length < 1 ? null
: values[0];
for (Iterator<FormValidator> itv = vs.iterator(); itv.hasNext();) {
FormValidator dv = itv.next();
if (dv.matches(field)) {
ValidateResult vr = dv.validate(ri.getRequest(), this,
value);
switch (vr.getType()) {
case PASS_CONTINUE:
continue;
case PASS_SKIP_PROPERTY:
break;
case PASS_SKIP_ALL:
return;
case FAILED_CONTINUE:
addError(field, vr.getMsg());
continue;
case FAILED_SKIP_PROPERTY:
addError(field, vr.getMsg());
break;
case FAILED_SKIP_ALL:
addError(field, vr.getMsg());
return;
}
}
}
}
}
public String getJsonChanges() throws JsonGenerationException,
JsonMappingException, IOException {
processBinders();
if (vecChanges == null)
return "{}";
return new ObjectMapper().writeValueAsString(vecChanges
.toArray(new Change[vecChanges.size()]));
}
/**
* 获取Spring为控制器生成参数值过程中建立的binder,以便将binder处理中遇到的错误合并到form中,
* 并且利用binder来获得经过conversion和format处理之后的参数值
*
* @param binder
*/
public void addBinder(WebDataBinder binder) {
binders.add(binder);
}
protected void mergeBindingResult(String field,
Collection<BindingResult> values) {
if (disabledFields.contains(field))
return;
HttpServletRequest request = ContextUtils.getRequest();
WebApplicationContext context = RequestContextUtils
.findWebApplicationContext(request);
Locale locale = RequestContextUtils.getLocale(request);
for (BindingResult val : values) {
if (!(val instanceof BeanPropertyBindingResult))
continue;
BeanPropertyBindingResult br = (BeanPropertyBindingResult) val;
if (br.getTarget() != null && br.getFieldType(field) != null
|| br.getObjectName().equals(field)) {
// merge Spring conversion & validation errors
List<FieldError> errors = br.getFieldErrors(field);
if (errors != null && errors.size() > 0) {
errors.forEach(p -> {
addError(field, context.getMessage(p, locale));
});
} else if (getErrors(field) == null && br.getTarget() != null) {
// no error, apply Spring formatter
String value = WinletDefaultFormattingConversionService
.format(br.getPropertyAccessor(), br.getTarget(),
field);
if (value != null)
setValue(field, value.toString());
}
break;
}
}
}
/**
* Merge Spring conversion / format / validation errors into form
*
* @param values
*/
protected void mergeBindingResult(Collection<BindingResult> values) {
if (ri.isValidateField()) {
mergeBindingResult(ri.getValidateFieldName(), values);
} else {
if (values != null) {
values.forEach(p -> bindingErrorCount += p.getErrorCount());
fields.forEach(p -> mergeBindingResult(p, values));
}
}
}
protected void processBinders() {
Collection<BindingResult> col = binders.stream()
.map(p -> p.getBindingResult()).collect(Collectors.toList());
binders.clear();
mergeBindingResult(col);
}
@Override
public boolean hasField(String field) {
return fields.contains(field);
}
@Override
public Form addError(String field, Function<String, Boolean> when,
String error, boolean validateEvenErrorExist) {
if (!validate(field))
return this;
if (hasError(field) && !validateEvenErrorExist)
return this;
if (when.apply(getValue(field)))
addError(field, error);
return this;
}
@Override
public Form addError(String field, boolean when, String error,
boolean validateEvenErrorExist) {
if (!validate(field))
return this;
if (hasError(field) && !validateEvenErrorExist)
return this;
if (when)
addError(field, error);
return this;
}
@Override
public Form addError(String field, Function<String, Boolean> when,
String error) {
return addError(field, when, error, false);
}
@Override
public Form addError(String field, boolean when, String error) {
return addError(field, when, error, false);
}
@Override
public String mapStatus(String vf, String vfError, String error,
String passed) {
if (isValidateField())
if (hasError())
return vfError;
else
return vf;
if (hasError())
return error;
return passed;
}
@Override
public String mapStatus(String vf, String vfError, String error) {
return mapStatus(vf, vfError, error, null);
}
@Override
public Form noError(Process process) throws Exception {
if (!hasError() && !isValidateField())
process.run();
return this;
}
}