/*
* Copyright 2012 E.J.I.E., S.A.
*
* Licencia con arreglo a la EUPL, Versión 1.1 exclusivamente (la «Licencia»);
* Solo podrá usarse esta obra si se respeta la Licencia.
* Puede obtenerse una copia de la Licencia en
*
* http://ec.europa.eu/idabc/eupl.html
*
* Salvo cuando lo exija la legislación aplicable o se acuerde por escrito,
* el programa distribuido con arreglo a la Licencia se distribuye «TAL CUAL»,
* SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ni expresas ni implícitas.
* Véase la Licencia en el idioma concreto que rige los permisos y limitaciones
* que establece la Licencia.
*/
package com.ejie.x38.validation;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Path;
import javax.validation.Path.Node;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import com.ejie.x38.json.JSONObject;
import com.ejie.x38.util.DateTimeManager;
import com.ejie.x38.util.StackTraceManager;
import com.ejie.x38.util.StaticsContainer;
/**
* Proporciona el API de validación de UDA.
*
* @author UDA
*
*/
public class ValidationManager {
private static final long serialVersionUID = 1L;
private final static Logger logger = LoggerFactory.getLogger(ValidationManager.class);
@Resource
private ReloadableResourceBundleMessageSource messageSource;
@Autowired(required=false)
private Validator validator;
@PostConstruct
public void init() {
if (this.validator==null){
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory();
this.validator = validatorFactory.getValidator();
}
}
/**
* Realiza la validación de la instancia indicada como parámetro. Las
* validaciones se realizan a partir de las anotaciones realizadas sobre las
* propiedades del bean. Los errores se añaden al objeto errors indicado
* como parámetro.
*
* @param errors
* Parámetro sobre el que se van a añadir los errores de
* validación que se produzcan.
* @param obj
* Intancia del objeto que se desea validar.
* @param groups
* Grupos de validacion que se van a utilizar para realizar la
* validación. En caso de no especificarse ninguno se tomará del
* grupo Default.class como grupo por defecto.
*/
public void validate(Errors errors, Object obj, Class<?>... groups) {
// En caso de no especificarse grupos de validaciones se toma el grupo Default por defecto.
if (groups == null || groups.length == 0 || groups[0] == null) {
groups = new Class<?>[] { Default.class };
}
// Se realiza la validacion de la instancia
Set<ConstraintViolation<Object>> violations = validator.validate(obj,
groups);
// A partir de las violaciones obtenidas a partir de la validacion
// realizada, se añaden en la propiedad Errors
for (ConstraintViolation<Object> v : violations) {
Path path = v.getPropertyPath();
String propertyName = "";
if (path != null) {
for (Node n : path) {
propertyName += n.getName() + ".";
}
propertyName = propertyName.substring(0,
propertyName.length() - 1);
}
String constraintName = v.getConstraintDescriptor().getAnnotation()
.annotationType().getSimpleName();
if (propertyName == null || "".equals(propertyName)) {
errors.reject(constraintName, v.getMessage());
} else {
errors.rejectValue(propertyName, constraintName, v.getMessage());
}
}
}
/**
* Método que permite realizar la validación de
* una propiedad del bean.
*
* @param bean
* Nombre del tipo del bean sobre el que se deben de validar los
* datos.
* @param property
* Nombre de la propiedad del bean que se debe de validar.
* @param value
* Valor de la propiedad que se debe de validar.
* @param locale
* Locale actual que define el idioma a utilizar en la
* internacionalización de los mensajes.
* @return Texto de error que contiene los mensajes de error resultantes de la validación.
*/
public String validateProperty(String bean, String property, String value, Locale locale){
try{
String capitalicedBean = StringUtils.capitalize(bean);
BeanWrapper beanWrapper = new BeanWrapperImpl(Class.forName(StaticsContainer.modelPackageName+capitalicedBean));
beanWrapper.setAutoGrowNestedPaths(true);
beanWrapper.setPropertyValue(property, value);
Set<ConstraintViolation<Object>> constraintViolations = validator.validateProperty(beanWrapper.getWrappedInstance(), property, Default.class);
return summary(constraintViolations, bean, locale);
}catch (Exception e) {
logger.error(StackTraceManager.getStackTrace(e));
return "error!";
}
}
/**
* Genera, a partir de unos errores indicados como parámetros, un mapa en el
* cual cada elemento está formado del siguiente modo:<br>
* <br>
* - El key del elemento del mapa está formado por el nombre de la propiedad
* sobre la que se ha producido el error de la validación o por el
* identificador que se ha asignado a la validación.<br>
* <br>
* - El objeto asociado al key está formado por una lista de mensajes de
* error internacionalizados a partir del key de error.
*
* @param errors
* Conjunto de errores que se desean procesar.
* @return Mapa resultante que contiene los errores procesados.
*/
public Map<String,List<String>> getErrorsAsMap(Errors errors){
// Se obtiene la locale actual que va a ser utilizada para realizar la
// internacionalización de los mensajes de error.
Locale locale = LocaleContextHolder.getLocale();
// Mapa en el que se van a almacenar los errores
Map<String,List<String>> errorsMap = new HashMap<String, List<String>>();
// Lista de errores que se han producido al realizarse el databinding
List<? extends ObjectError> fieldErrors = errors.getAllErrors();
// Se recorre cada error
for (Iterator<? extends ObjectError> iterator = fieldErrors.iterator(); iterator.hasNext();) {
ObjectError objectError = (ObjectError) iterator.next();
String errorMessage;
String key;
// Se comprueba si el error está asociado a la validación de una
// propiedad de un bean
if (objectError instanceof FieldError){
FieldError fieldError = (FieldError)objectError;
// En caso de que se trate de un FieldError se toma el nombre
// del campo como el key que se utilizará en el elemento del
// mapa.
key = fieldError.getField();
// Se trata de obtener el mensaje internacionalizado a partir del key del error.
try{
// En caso de existir se toma el mensaje internacionalizado como el texto de error a mostrar.
errorMessage = messageSource.getMessage(fieldError.getDefaultMessage(), null, locale);
}catch (NoSuchMessageException e) {
// En caso de producirse un error en la obtención del texto se toma el key como texto a mostrar.
errorMessage = fieldError.getCode();
}
}else{
// En caso de que se trate de un FieldError se toma el code
// del error como el key que se utilizará en el elemento del
// mapa.
key = objectError.getCode();
// Se trata de obtener el mensaje internacionalizado a partir del key del error.
try{
// En caso de existir se toma el mensaje internacionalizado como el texto de error a mostrar.
errorMessage = messageSource.getMessage(objectError.getDefaultMessage(), null, locale);
}catch (NoSuchMessageException e) {
// En caso de producirse un error en la obtención del texto se toma el key como texto a mostrar.
errorMessage = objectError.getDefaultMessage();
}
}
// Se comprrueba si error se ha insertado y a en el mapa
if(errorsMap.containsKey(key)){
// En caso de existir se añade el mensaje de error en la lista de errores.
errorsMap.get(key).add(errorMessage);
}else{
// En caso de no existir se genera un nuevo elemento en el mapa
// con una lista de errores en la cual se añade el mensaje de
// error.
List<String> listaErrores = new ArrayList<String>();
listaErrores.add(errorMessage);
errorsMap.put(key,listaErrores);
}
}
// Se devuelve el mapa de errores
return errorsMap;
}
/**
* Genera, a partir de unos errores indicados como parámetros, una lista
* formada por los mensajes de error internacionalizados.
*
* @param errors
* Conjunto de errores que se desean procesar.
* @return Lista resultanete que contiene los mensajes de error procesados.
*/
public List<String> getErrorsAsList(Errors errors){
// Se obtiene la locale actual que va a ser utilizada para realizar la
// internacionalización de los mensajes de error.
Locale locale = LocaleContextHolder.getLocale();
// Mapa en el que se van a almacenar los errores
List<String> list = new ArrayList<String>();
// Lista de errores que se han producido al realizarse el databinding
List<FieldError> fieldErrors = errors.getFieldErrors();
// Se recorre cada error
for (Iterator<FieldError> iterator = fieldErrors.iterator(); iterator.hasNext();) {
FieldError fieldError = (FieldError) iterator.next();
String errorMessage;
try{
errorMessage = messageSource.getMessage(fieldError.getCode(), null, locale);
}catch (NoSuchMessageException e) {
errorMessage = fieldError.getCode();
}
list.add(errorMessage);
}
return list;
}
/**
* Genera a partir de un mensaje y un estilo, una estructura para devolver
* una estructura que pueda ser convertida a formato JSON e interpretada por
* el feedback.
*
* @param msg
* Mensaje que debe ser visualizado por el feedback.
* @param style
* Estilo que se debe mostrar con el feedback.
* @return Estrutura que contiene el mensaje y el estilo que se debe de
* visualizar en el feedback.
*/
public Map<String,Object> getRupFeedbackMsg(String msg, String style){
Map<String,Object> map = new HashMap<String, Object>();
map.put("label", msg);
if (style!=null && !"".equals(style)){
map.put("style", style);
}
return map;
}
/*
* Metodos de validaciones
*/
public JSONObject getMessageJSON(Object fieldErrors){
return this.getMessageJSON(fieldErrors, null, null);
}
public JSONObject getMessageJSON(Object fieldErrors, Object feedbackMessage){
return this.getMessageJSON(fieldErrors, feedbackMessage, null);
}
public JSONObject getMessageJSON(Object fieldErrors, Object feedbackMessage, String style){
JSONObject message = new JSONObject();
if (feedbackMessage!=null ){
JSONObject feedbackObj = new JSONObject();
feedbackObj.put("message", feedbackMessage);
if (style!=null && !"".equals(style)){
feedbackObj.put("style", style);
}
message.put("rupFeedback", feedbackObj);
}
if (fieldErrors!=null){
message.put("rupErrorFields", fieldErrors);
}
return message;
}
/**
* Método utilizado por el ValidationFilter para realizar la validación de
* la información enviada en la request.
*
* @param bean
* Nombre del tipo del bean sobre el que se deben de validar los
* datos.
* @param data
* Datos enviados en la petición.
* @param locale
* Locale actual que define el idioma a utilizar en la
* internacionalización de los mensajes.
* @return Texto de error que contiene los mensajes de error y que van a ser
* enviados por el ValidationFilter.
*/
@Deprecated
public String validateObject(String bean, String data, Locale locale){
try{
Class<?> clazz = Class.forName(StaticsContainer.modelPackageName+bean);
ObjectMapper mapper = new ObjectMapper();
Object instance = mapper.readValue(data, clazz);
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(instance);
return summary(constraintViolations, bean, locale);
}catch (Exception e) {
logger.error(StackTraceManager.getStackTrace(e));
return "error!";
}
}
/**
* Realiza la validación del objeto indicado y envía en la response los
* errores de validación que se han producido.
*
* @param <T>
* Tipo del objeto que se va a validar.
* @param object
* Objeto sobre el que se va a realizar la validación.
* @param response
* HttpServletResponse sobre la que se van a escribir los errores
* de validación.
* @return True/false dependiendo del resultado de la validación.
*/
@Deprecated
public <T extends Object> Boolean writeValidationErrors(T object, HttpServletResponse response){
Set<ConstraintViolation<T>> constraintViolations = validator.validate(object);
if (!constraintViolations.isEmpty()){
String summary = this.summary(constraintViolations, object.getClass().getSimpleName(), LocaleContextHolder.getLocale());
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
response.setContentType("text/javascript;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", DateTimeManager.getHttpExpiredDate());
try {
response.getWriter().write(summary);
return true;
} catch (IOException e) {
logger.error(StackTraceManager.getStackTrace(e));
return true;
}
}
return false;
}
// MÉTODOS PRIVADOS
/**
* Genera una cadena de error en formato JSON a partir de los errores de
* validación que se han producido.
*
* @param <T>
* Tipo del objeto que se va a validar.
* @param constraintViolations
* Errores de validación sobre los que se desea generar el
* mensaje.
* @param bean
* Nombre del tipo de bean sobre el que se ha realizado la
* validación.
* @param locale
* Locale actual que define el idioma a utilizar en la
* internacionalización de los mensajes.
* @return Cadena de error resultante.
*/
private <T extends Object> String summary (Set<ConstraintViolation<T>> constraintViolations, String bean, Locale locale){
Iterator<ConstraintViolation<T>> ite = constraintViolations.iterator();
Map<String,List<Map<String,String>>> errors = new HashMap<String,List<Map<String,String>>>();
String propertyKey ="";
List<Map<String,String>> propertyErrors;
while (ite.hasNext()) {
ConstraintViolation<T> constraintViolation = ite.next();
propertyKey = constraintViolation.getPropertyPath()+"";
if(errors.containsKey(propertyKey)){
propertyErrors = errors.get(propertyKey);
}else{
propertyErrors = new ArrayList<Map<String,String>>();
}
Map<String,String> node = new HashMap<String,String>();
String interpolatedMessage;
//Try to get the interpolated Message in this order: 1- War, 2- EAR, 3- Hibernate's Default
try{
interpolatedMessage = messageSource.getMessage(constraintViolation.getMessage(), null, locale);
}catch(NoSuchMessageException e){
interpolatedMessage = constraintViolation.getMessage();
}
if(interpolatedMessage!= null && !interpolatedMessage.equals("")){
node.put(constraintViolation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(), interpolatedMessage);
}else{
node.put(constraintViolation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(), "message not found");
logger.error("Validation message for key "+constraintViolation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName()+" not found");
}
propertyErrors.add(node);
errors.put(propertyKey, propertyErrors);
}
if (!errors.isEmpty()){
Map<String, String> title = new HashMap<String, String>();
title.put("key", bean);
String header = serialize(title);
String body = serialize(errors);
String[] result = {header, body};
String summary = serialize(result);
return summary;
}else{
return null;
}
}
/**
* Metodo utilizado para realizar la serialización de un objeto en formato JSON.
*
* @param obj Objeto a serializar.
* @return Serialización del objeto en formato JSON.
*/
private String serialize(Object obj) {
StringWriter sw = new StringWriter();
ObjectMapper mapper = new ObjectMapper();
MappingJsonFactory jsonFactory = new MappingJsonFactory();
try {
JsonGenerator jsonGenerator = jsonFactory.createGenerator(sw);
mapper.writeValue(jsonGenerator, obj);
sw.close();
return sw.getBuffer().toString();
} catch (IOException e) {
logger.error(StackTraceManager.getStackTrace(e));
return "error!";
}
}
}