2009-03-27 12 views
13

Sto utilizzando Spring per l'input e la convalida del modulo. Il comando del modulo di controllo contiene il modello che si sta modificando. Alcuni degli attributi del modello sono di tipo personalizzato. Ad esempio, il numero di previdenza sociale della persona è un tipo SSN personalizzato.Convalida della primavera, come fare in modo che PropertyEditor generi un messaggio di errore specifico

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

e avvolgente Persona in una forma comando Primavera edit:

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

Dalla primavera non sa come convertire testo in uno SSN, registro un editor di cliente con legante del controller modulo:

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

e SsnEditor è solo una consuetudine java.beans.PropertyEditor che può convertire il testo in un oggetto di 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 
    } 
} 

Se setAsText incontri testo che non è valido e non può essere convertito in uno SSN, poi si getta IllegalArgumentException (per PropertyEditorsetAsText s' specifica). Il problema che sto avendo è che la conversione da testo a oggetto (tramite PropertyEditor.setAsText()) ha luogo prima del viene chiamato il mio validatore Spring. Quando setAsText genera IllegalArgumentException, Spring visualizza semplicemente il messaggio di errore generico definito in errors.properties. Quello che voglio è un messaggio di errore specifico che dipende dal motivo esatto per cui il SSN inserito non è valido. PropertyEditor.setAsText() determinerebbe il motivo. Ho provato a incorporare il testo del motivo dell'errore nel testo di IllegalArgumentException, ma Spring lo tratta semplicemente come un errore generico.

C'è una soluzione a questo? Per ripetere, ciò che voglio è lo specifico messaggio di errore generato dallo PropertyEditor da far apparire sul messaggio di errore nel modulo Spring. L'unica alternativa che posso pensare è di memorizzare l'SSN come testo nel comando ed eseguire la convalida nel validatore. Il testo per la conversione degli oggetti SSN avverrà nel formato onSubmit. Questo è meno desiderabile in quanto la mia forma (e il modello) ha molte proprietà e non voglio dover creare e mantenere un comando che ha ogni attributo di modello come un campo di testo.

Quanto sopra è solo un esempio, il mio codice reale non è Persona/SSN, quindi non c'è bisogno di rispondere con "Perché non conservare SSN come testo ..."

+0

@Steve Kuo Aggiunto alla risposta originale –

risposta

6

Stai provando a fare la convalida in un raccoglitore. Questo non è lo scopo del rilegatore. Si suppone che un legatore leghi i parametri di richiesta al proprio oggetto di supporto, nient'altro. Un editor di proprietà converte le stringhe in oggetti e viceversa - non è progettato per fare altro.

In altre parole, è necessario considerare la separazione delle preoccupazioni: si sta tentando di integrare la funzionalità in un oggetto che non avrebbe mai dovuto fare altro che convertire una stringa in un oggetto e viceversa.

Si potrebbe prendere in considerazione la possibilità di suddividere l'oggetto SSN in più campi validabili facilmente vincolanti (oggetti stringa, oggetti di base come date, ecc.). In questo modo è possibile utilizzare un validatore dopo l'associazione per verificare che l'SSN sia corretto oppure è possibile impostare direttamente un errore. Con un editor di proprietà, si lancia un IllegalArgumentException, Spring lo converte in un errore di mancata corrispondenza di tipo perché è quello che è: la stringa non corrisponde al tipo che è previsto. Questo è tutto ciò che è. Un validatore, d'altra parte, può farlo. È possibile utilizzare il tag bind spring per eseguire il binding ai campi nidificati, purché venga compilata l'istanza SSN: è necessario prima inizializzarla con new(). Per esempio:

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

Se veramente si vuole insistere su questa strada, però, avere il vostro editor di proprietà di tenere un elenco di errori - se si tratta di gettare un IllegalArgumentException, aggiungerlo alla lista e poi gettare l'IllegalArgumentException (prendere e rilanciare se necessario). Poiché è possibile costruire l'editor di proprietà nello stesso thread del binding, sarà sicuro se si sostituisce semplicemente il comportamento predefinito dell'editor delle proprietà: è necessario trovare il hook che utilizza per eseguire il binding e sovrascriverlo: eseguire lo stesso editor delle proprietà registrazione che stai facendo ora (tranne che nello stesso metodo, in modo da poter mantenere il riferimento al tuo editor) e poi alla fine del binding, puoi registrare gli errori recuperando l'elenco dal tuo editor se fornisci un accesso pubblico . Una volta che l'elenco è stato recuperato, puoi elaborarlo e aggiungere di conseguenza i tuoi errori.

+3

Avevo paura di questo e sono un po 'deluso dalla gestione della forma di Spring. Da quello che sto ascoltando, l'approccio "corretto" di primavera è di eseguire la convalida prima del legame. Quindi questo significa creare un comando di form con campi stringa. –

+0

Continua dal commento precedente: Il validatore convalida questi campi. OnSubmit del controller convertirà quindi i campi stringa nel loro tipo corretto e imposterà il valore sul modello di supporto. Questo è un dolore perché ora devo ricreare ogni campo modificabile come una stringa nel comando del form. –

+0

No, la convalida dopo il bind - l'associazione non dovrebbe convalidare nulla - se lo vuoi, un altro approccio alternativo è creare una sorta di lista di errori nel tuo specifico oggetto e memorizzare gli errori al suo interno quando ti leghi, e il validatore controlla questo elenco ... – MetroidFan2002

0

Questo suona simile a una questione che avuto con NumberFormatExceptions quando il valore per una proprietà intera non poteva essere associato se, per esempio, una stringa era stata inserita nel modulo. Il messaggio di errore sul modulo era un messaggio generico per quell'eccezione.

La soluzione era aggiungere il proprio bundle di risorse del messaggio al contesto dell'applicazione e aggiungere il mio messaggio di errore per le mancate corrispondenze di tipo su tale proprietà. Forse puoi fare qualcosa di simile per IllegalArgumentExceptions su un campo specifico.

5

Come detto:

Quello che voglio è il messaggio errore specifico generato dal PropertyEditor alla superficie per il messaggio di errore nel modulo di primavera

Dietro le quinte, usi Spring MVC una strategia BindingErrorProcessor per l'elaborazione degli errori di campo mancanti e per la traduzione di PropertyAccessException in un FieldError. Quindi, se si desidera sovrascrivere di default primavera strategia MVC BindingErrorProcessor, è necessario fornire una strategia BindingErrorProcessor in base a:

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); 
    } 

} 

Al fine di testare, facciamo il seguente

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!"); 
     } 

    }); 
} 

Ora la nostra classe Test

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!"); 
    } 

} 

saluti,

+0

Affinché funzioni per me, dovevo avere BindingResult sulla firma del metodo di processo anziché BindException: 'public void processPropertyAccessException (PropertyAccessException accessException, BindingResult bindingResult)' – carlosHT

0

credo che si poteva solo tr y per mettere questo nel vostro sorgente del messaggio:

typeMismatch.person.ssn = sbagliato formato SSN

1

Per dare seguito alla risposta di @Arthur Ronald, questo è come ho finito per l'attuazione del presente:

On il controllore:

setBindingErrorProcessor(new CustomBindingErrorProcessor()); 

E allora la classe vincolante processore errore:

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); 
     } 

    } 

}   

Quindi la firma del metodo del processore accetta un BindingResult anziché un BindException su questa versione.

Problemi correlati