2009-03-27 13 views
13

Estoy usando Spring para la entrada y validación de formularios. El comando del controlador de formulario contiene el modelo que se está editando. Algunos de los atributos del modelo son un tipo personalizado. Por ejemplo, el número de la seguridad social de la persona es un tipo de SSN personalizado.Validación de primavera, cómo hacer que PropertyEditor genere un mensaje de error específico

public class Person { 
    public String getName() {...} 
    public void setName(String name) {...} 
    public SSN getSocialSecurtyNumber() {...} 
    public void setSocialSecurtyNumber(SSN ssn) {...} 
} 

, embalaje Persona en un comando de la primavera de edición:

public class EditPersonCommand { 
    public Person getPerson() {...} 
    public void setPerson(Person person) {...} 
} 

desde la primavera no se sabe cómo convertir texto a un número de seguro social, registro un editor de cliente con la carpeta del controlador de formulario:

public class EditPersonController extends SimpleFormController { 
    protected void initBinder(HttpServletRequest req, ServletRequestDataBinder binder) { 
     super.initBinder(req, binder); 
     binder.registerCustomEditor(SSN.class, "person.ssn", new SsnEditor()); 
    } 
} 

y SsnEditor es sólo una costumbre java.beans.PropertyEditor que puede convertir texto a un objeto SSN:

public class SsnEditor extends PropertyEditorSupport { 
    public String getAsText() {...} // converts SSN to text 
    public void setAsText(String str) { 
     // converts text to SSN 
     // throws IllegalArgumentException for invalid text 
    } 
} 

Si setAsText encuentros texto que es válido y no se puede convertir a un SSN, entonces lanza IllegalArgumentException (por PropertyEditorsetAsText 's especificación). El problema que estoy teniendo es que la conversión de texto a objeto (a través de PropertyEditor.setAsText()) se lleva a cabo antes de se llama mi validador de Spring. Cuando setAsText arroja IllegalArgumentException, Spring simplemente muestra el mensaje de error genérico definido en errors.properties. Lo que quiero es un mensaje de error específico que depende de la razón exacta por la cual el SSN ingresado no es válido. PropertyEditor.setAsText() determinaría la razón. Intenté incrustar el texto de la razón de error en el texto de IllegalArgumentException, pero Spring solo lo trata como un error genérico.

¿Hay alguna solución a esto? Para repetir, lo que quiero es el mensaje de error específico generado por el PropertyEditor para que aparezca el mensaje de error en el formulario de Spring. La única alternativa que puedo pensar es almacenar el SSN como texto en el comando y realizar la validación en el validador. La conversión de objeto de texto a SSN se llevaría a cabo en el formulario onSubmit. Esto es menos deseable ya que mi formulario (y modelo) tiene muchas propiedades y no quiero tener que crear y mantener un comando que tenga todos y cada uno de los atributos del modelo como un campo de texto.

Lo anterior es sólo un ejemplo, mi código actual no es persona/SSN, así que no hay necesidad de responder con "¿por qué no almacenar SSN como texto ..."

+0

@Steve Kuo Agregado a la respuesta original –

Respuesta

6

Está tratando de hacer la validación en una carpeta. Ese no es el propósito del encuadernador. Se supone que una carpeta debe vincular los parámetros de solicitud a su objeto de respaldo, nada más. Un editor de propiedades convierte Strings en objetos y viceversa, no está diseñado para hacer nada más.

En otras palabras, debe tener en cuenta la separación de las preocupaciones: está tratando de calzar la funcionalidad en un objeto que nunca tuvo la intención de hacer nada más que convertir una cadena en un objeto y viceversa.

Puede considerar dividir su objeto SSN en múltiples campos validados que sean fáciles de enlazar (objetos String, objetos básicos como fechas, etc.). De esta forma, puede usar un validador después del enlace para verificar que el SSN sea correcto, o puede establecer un error directamente. Con un editor de propiedades, arrojas una IllegalArgumentException, Spring lo convierte en un error de desajuste de tipo porque eso es lo que es: la cadena no coincide con el tipo que se espera. Eso es todo lo que es. Un validador, por otro lado, puede hacer esto. Puede usar la etiqueta de enlace de resorte para enlazar a los campos anidados, siempre que la instancia de SSN esté llena; primero debe inicializarse con new(). Por ejemplo:

<spring:bind path="ssn.firstNestedField">...</spring:bind> 

Si realmente quiere persistir en este camino, sin embargo, que su editor de propiedades mantener una lista de errores - si se trata de lanzar una IllegalArgumentException, añadirlo a la lista y luego tirar el IllegalArgumentException (atrapar y volver a lanzar si es necesario). Como puede construir su editor de propiedades con el mismo hilo que el enlace, será seguro si reemplaza el comportamiento predeterminado del editor de propiedades; necesita encontrar el enlace que usa para hacer el enlace y anularlo, haga el mismo editor de propiedades. registro que está haciendo ahora (excepto en el mismo método, para que pueda mantener la referencia a su editor) y luego al final de la vinculación, puede registrar errores recuperando la lista de su editor si proporciona un acceso público . Una vez que se recupera la lista, puede procesarla y agregar sus errores en consecuencia.

+3

Tenía miedo de esto y estoy un poco decepcionado con el manejo del formulario de Spring. Por lo que estoy escuchando, el enfoque Spring "correcto" es realizar la validación antes del enlace. Entonces esto significa crear un comando de formulario con campos de cadena. –

+0

Continúa de comentario anterior: El validador valida estos campos. El onSubmit del controlador convertiría los campos de cadena a su tipo correcto y establecería el valor en el modelo de respaldo. Esto es un problema porque ahora tengo que volver a crear cada campo editable como una cadena en el comando del formulario. –

+0

No, validar después de vincular: no se supone que el enlace valide nada; si lo desea, otro enfoque alternativo es crear algún tipo de lista de errores en su objeto específico y almacenar los errores en él cuando se vincula y tener el validador verifique esta lista ... – MetroidFan2002

0

Esto suena similar a un problema que tenía con NumberFormatExceptions cuando el valor de una propiedad entera no podía vincularse si, por ejemplo, se ingresó una Cadena en el formulario. El mensaje de error en el formulario era un mensaje genérico para esa excepción.

La solución fue agregar mi propio paquete de recursos de mensaje al contexto de mi aplicación y agregar mi propio mensaje de error para las discrepancias de tipo en esa propiedad. Quizás pueda hacer algo similar para IllegalArgumentExceptions en un campo específico.

5

Como se ha dicho:

Lo que yo quiero es el mensaje error específico generado por el PropertyEditor a la superficie para el mensaje de error en el formulario de primavera

Detrás de las escenas, utiliza Spring MVC una estrategia BindingErrorProcessor para procesar errores de campo faltantes, y para traducir una PropertyAccessException a un FieldError. Así que si usted quiere redefinir el valor predeterminado primavera estrategia MVC BindingErrorProcessor, debe proporcionar una estrategia BindingErrorProcessor acuerdo con:

public class CustomBindingErrorProcessor implements DefaultBindingErrorProcessor { 

    public void processMissingFieldError(String missingField, BindException errors) { 
     super.processMissingFieldError(missingField, errors); 
    } 

    public void processPropertyAccessException(PropertyAccessException accessException, BindException errors) { 
     if(accessException.getCause() instanceof IllegalArgumentException) 
      errors.rejectValue(accessException.getPropertyChangeEvent().getPropertyName(), "<SOME_SPECIFIC_CODE_IF_YOU_WANT>", accessException.getCause().getMessage()); 
     else 
      defaultSpringBindingErrorProcessor.processPropertyAccessException(accessException, errors); 
    } 

} 

Con el fin de probar, vamos a hacer lo siguiente

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) { 
    binder.registerCustomEditor(SSN.class, new PropertyEditorSupport() { 

     public String getAsText() { 
      if(getValue() == null) 
       return null; 

      return ((SSN) getValue()).toString(); 
     } 

     public void setAsText(String value) throws IllegalArgumentException { 
      if(StringUtils.isBlank(value)) 
       return; 

      boolean somethingGoesWrong = true; 
      if(somethingGoesWrong) 
       throw new IllegalArgumentException("Something goes wrong!"); 
     } 

    }); 
} 

Ahora nuestra clase de prueba

public class PersonControllerTest { 

    private PersonController personController; 
    private MockHttpServletRequest request; 

    @BeforeMethod 
    public void setUp() { 
     personController = new PersonController(); 
     personController.setCommandName("command"); 
     personController.setCommandClass(Person.class); 
     personController.setBindingErrorProcessor(new CustomBindingErrorProcessor()); 

     request = new MockHttpServletRequest(); 
     request.setMethod("POST"); 
     request.addParameter("ssn", "somethingGoesWrong"); 
    } 

    @Test 
    public void done() { 
     ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse()); 

     BindingResult bindingResult = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command"); 

     FieldError fieldError = bindingResult.getFieldError("ssn"); 

     Assert.assertEquals(fieldError.getMessage(), "Something goes wrong!"); 
    } 

} 

cordiales,

+0

Para que funcione para mí, tuve que tener el BindingResult en mi firma de método de proceso en lugar de BindException: 'public void processPropertyAccessException (PropertyAccessException accessException, BindingResult bindingResult)' – carlosHT

0

creo que sólo podría tr Y para poner esto en su origen del mensaje:

typeMismatch.person.ssn = Formato incorrecto SSN

1

Como seguimiento a la respuesta de @Arthur Ronald, esta es la forma en que terminó la aplicación de esta:

En el controlador:

setBindingErrorProcessor(new CustomBindingErrorProcessor()); 

Y entonces la clase de procesador de error de enlace:

public class CustomBindingErrorProcessor extends DefaultBindingErrorProcessor { 

    public void processPropertyAccessException(PropertyAccessException accessException, 
               BindingResult bindingResult) { 

     if(accessException.getCause() instanceof IllegalArgumentException){ 

      String fieldName = accessException.getPropertyChangeEvent().getPropertyName(); 
      String exceptionError = accessException.getCause().getMessage(); 

      FieldError fieldError = new FieldError(fieldName, 
                "BINDING_ERROR", 
                fieldName + ": " + exceptionError); 

      bindingResult.addError(fieldError); 
     }else{ 
      super.processPropertyAccessException(accessException, bindingResult); 
     } 

    } 

}   

Por lo tanto, la firma del método del procesador toma un BindingResult en lugar de una BindException en esta versión.

Cuestiones relacionadas