/*
* Copyright 2014 astamuse company,Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.astamuse.asta4d.web.form.validation;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ElementKind;
import javax.validation.Path;
import javax.validation.Path.Node;
import javax.validation.Validation;
import javax.validation.Validator;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import com.astamuse.asta4d.util.annotation.AnnotatedPropertyInfo;
import com.astamuse.asta4d.util.annotation.AnnotatedPropertyUtil;
import com.astamuse.asta4d.util.collection.ListConvertUtil;
import com.astamuse.asta4d.util.collection.RowConvertor;
import com.astamuse.asta4d.util.i18n.pattern.CharsetResourceBundleFactory;
import com.astamuse.asta4d.util.i18n.pattern.ResourceBundleFactory;
import com.astamuse.asta4d.web.WebApplicationContext;
import com.astamuse.asta4d.web.form.annotation.CascadeFormField;
public class JsrValidator extends CommonValidatorBase implements FormValidator {
protected static class Asta4DResourceBundleFactoryAdapter implements ResourceBundleLocator {
private String baseName;
private ResourceBundleFactory resourceBundleFactory;
public Asta4DResourceBundleFactoryAdapter(String baseName) {
this(baseName, new CharsetResourceBundleFactory(StandardCharsets.UTF_8));
}
public Asta4DResourceBundleFactoryAdapter(String baseName, ResourceBundleFactory resourceBundleFactory) {
this.baseName = baseName;
this.resourceBundleFactory = resourceBundleFactory;
}
@Override
public ResourceBundle getResourceBundle(Locale locale) {
try {
return resourceBundleFactory.retrieveResourceBundle(baseName, locale);
} catch (MissingResourceException mre) {
return null;
}
}
}
protected static class Asta4DIntegratedResourceBundleInterpolator extends ResourceBundleMessageInterpolator {
public Asta4DIntegratedResourceBundleInterpolator() {
super();
}
public Asta4DIntegratedResourceBundleInterpolator(ResourceBundleLocator userResourceBundleLocator, boolean cacheMessages) {
super(userResourceBundleLocator, cacheMessages);
}
public Asta4DIntegratedResourceBundleInterpolator(ResourceBundleLocator userResourceBundleLocator) {
super(userResourceBundleLocator);
}
protected Locale retrieveLocalFromAsta4d() {
Locale loc = WebApplicationContext.getCurrentThreadWebApplicationContext().getCurrentLocale();
if (loc == null) {
return Locale.getDefault();
} else {
return loc;
}
}
@Override
public String interpolate(String message, Context context) {
return super.interpolate(message, context, retrieveLocalFromAsta4d());
}
@Override
public String interpolate(String message, Context context, Locale locale) {
return super.interpolate(message, context, retrieveLocalFromAsta4d());
}
}
protected static class ValidationPropertyInfo {
Path path;
AnnotatedPropertyInfo field;
int[] indexes;
public ValidationPropertyInfo(Path path, AnnotatedPropertyInfo field, int[] indexes) {
super();
this.path = path;
this.field = field;
this.indexes = indexes;
}
}
protected Validator validator;
public JsrValidator() {
this(retrieveDefaultValidator());
}
public JsrValidator(Validator validator) {
this(validator, true);
}
public JsrValidator(Validator validator, boolean addFieldLablePrefixToMessage) {
super(addFieldLablePrefixToMessage);
this.validator = validator;
}
private static Validator defaultValidator = null;
protected static Validator retrieveDefaultValidator() {
// as an out-of-box default implementation, we do not mind we may create the instance many times.
if (defaultValidator == null) {
defaultValidator = Validation.byDefaultProvider().configure()
.messageInterpolator(new Asta4DIntegratedResourceBundleInterpolator()).buildValidatorFactory().getValidator();
}
return defaultValidator;
}
@Override
public List<FormValidationMessage> validate(final Object form) {
Set<ConstraintViolation<Object>> cvs = validator.validate(form);
return ListConvertUtil.transform(cvs, new RowConvertor<ConstraintViolation<Object>, FormValidationMessage>() {
@Override
public FormValidationMessage convert(int rowIndex, ConstraintViolation<Object> cv) {
ValidationPropertyInfo vp = retrieveValidationPropertyInfo(form.getClass(), cv.getPropertyPath());
String fieldName;
String msg;
if (vp.field == null) {
// which means we cannot retrieve the annotated form field information, thus we got a unpredicated validation error
fieldName = vp.path.toString();
msg = cv.getMessage();
} else {
fieldName = retrieveFieldName(vp.field, vp.indexes);
String fieldLabel = retrieveFieldLabel(vp.field, vp.indexes);
String annotatedMsg = retrieveFieldAnnotatedMessage(vp.field);
if (StringUtils.isNotEmpty(annotatedMsg)) {
msg = createAnnotatedMessage(vp.field.getType(), fieldName, fieldLabel, annotatedMsg);
} else {
String fieldTypeName = retrieveFieldTypeName(vp.field);
msg = createMessage(vp.field.getType(), fieldName, fieldLabel, fieldTypeName, cv.getMessage());
}
}
return new FormValidationMessage(fieldName, msg);
}
});
}
@SuppressWarnings("rawtypes")
protected String createMessage(Class formCls, String fieldName, String fieldLabel, String fieldTypeName, String cvMsg) {
if (addFieldLablePrefixToMessage) {
String msgTemplate = "%s: %s";
return String.format(msgTemplate, fieldLabel, cvMsg);
} else {
return cvMsg;
}
}
protected ValidationPropertyInfo retrieveValidationPropertyInfo(Class<?> formCls, Path path) {
Iterator<Node> it = path.iterator();
Class<?> cls = formCls;
int[] indexes = EMPTY_INDEXES;
try {
while (it.hasNext()) {
Node node = it.next();
if (node.getKind() != ElementKind.PROPERTY) {
// we cannot handle this case
return new ValidationPropertyInfo(path, null, indexes);
}
String name = node.getName();
if (it.hasNext()) {// not the last
AnnotatedPropertyInfo field = AnnotatedPropertyUtil.retrievePropertyByBeanPropertyName(cls, name).get(0);
CascadeFormField cff = field.getAnnotation(CascadeFormField.class);
if (cff == null) {
// regular fields
cls = field.getType();
} else if (StringUtils.isEmpty(cff.arrayLengthField())) {
// simple cascading
cls = field.getType();
} else {
// array cascading
cls = field.getType().getComponentType();
if (node.getIndex() != null) {
indexes = ArrayUtils.add(indexes, node.getIndex().intValue());
}
}
continue;
} else {// the last
AnnotatedPropertyInfo field = AnnotatedPropertyUtil.retrievePropertyByBeanPropertyName(cls, name).get(0);
if (field == null) {
// it seems we got a unexpected error
return new ValidationPropertyInfo(path, null, indexes);
} else {
if (node.getIndex() == null) {
// regular fields or simple cascading
return new ValidationPropertyInfo(path, field, indexes);
} else {
return new ValidationPropertyInfo(path, field, ArrayUtils.add(indexes, node.getIndex().intValue()));
}
}
}
}
// it seems impossible
return new ValidationPropertyInfo(path, null, indexes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected String retrieveFieldDisplayName(String fieldName) {
return fieldName;
}
}