Spring MVC: manage BindingException - java

I have a problem with the binding of a form input text to a Integer field of the bean to which the form is binded. If I write a wrong number in the input text (eg: "12b") I have a Binding Exception. So, I set a #InitBinder in my controller in this way:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Integer.class, new CustomIntegerBinder());
}
Where CustomIntegerBinder is implemented as follows:
public class CustomIntegerBinder extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(Integer.parseInt(text));
} catch (Exception e) {
//I WANT TO ADD ERROR TO THE ERROR LIST!
}
}
#Override
public String getAsText() {
return getValue().toString();
}
}
My question is: how could I succeed in adding a message error to the errors list, so that a conversion error would not cause a crasch of the application, but a message to be printed in the "errors" tag in the jsp?
Thank you!

The setAsText method should throw an IllegalArgumentException (as the method signature indicates) if the value can't be set from text.
If you throw the IllegalArgumentException from your catch then Spring should add the error for you.

Related

vaadin cross form validation fails as bean not bound

<vaadin.version>22.0.7</vaadin.version>
I have built a vaadin edit form with an attached Save button.
When the user clicks 'Save' I want to validate the form and then save the validated data to the bean.
This works as expected.
The problem comes when I went to add in cross field validation. In this case I want to validate that a start/end date pair are in the correct order.
The problem is that when I add the form validator, vaadin starts throwing exceptions when I call validate.
#Override
public void bindFields(CrudFieldBinder<DiscountCode> binder)
{
binder.forField(this.discountCode).asRequired("Please enter the unique Discount Code.").bind(
DiscountCode::getDiscountCode,
DiscountCode::setDiscountCode);
binder.forField(this.startDate).asRequired("Please enter a Start Date.").bind(DiscountCode::getStartDate,
DiscountCode::setStartDate);
binder.forField(this.endDate).asRequired("Please enter an End Date.")
.bind(DiscountCode::getEndDate,
DiscountCode::setEndDate);
binder.withValidator(new DateRangeValidator());
}
I've tried a few variations all with the same result. Here is the latest iteration:
protected void saveEdits(E currentEntity) {
try
{
binder.writeBean(currentEntity);
}
catch (ValidationException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
// this line throws the below error
BinderValidationStatus<E> status = binder.validate();
}
The call to writeBean runs without error but the call to binder.validate() fails with:
java.lang.IllegalStateException: Cannot validate binder: bean level validators have been configured but no bean is currently set
at com.vaadin.flow.data.binder.Binder.validate(Binder.java:2479) ~[flow-data-9.0.8.jar:9.0.8]
at com.vaadin.flow.data.binder.Binder.validate(Binder.java:2463) ~[flow-data-9.0.8.jar:9.0.8]
... EditorFormLayout.saveEdits(EditorFormLayout.java:92) ~[classes/:?]
This seems to suggest that form level validation only works if you make a call to setBean, however my understanding is the call to setBean will result in the form autosaving rather than waiting for the user to click the save button.
I don't believe there is a supported method for doing this.
I've raised a feature request here:
https://github.com/vaadin/platform/issues/2868
Here is my hack that relies on overloading the binder and making the validation method think a bean is bound.
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.BinderValidationStatus;
public class MyBinder<E> extends Binder<E>
{
public MyBinder()
{
}
/**
* Apparently when doing cross field validation by calling
* binder.withValidator there is no way to the list of errors back
* due to the following issue.
* https://github.com/vaadin/platform/issues/2868
*
* Which essentially says that unless you are using setBean you can't
* do cross form validation.
*
* This code caches the bean sent to the binder when readBean is called
* and then uses that bean to fake a bound bean during validation.
*
*/
private boolean validating = false;
private E bean;
#Override
public void readBean(E bean)
{
this.bean = bean;
super.readBean(bean);
}
#Override
public void setBean(E bean)
{
throw new RuntimeException("The MyBinder only works with read/writeBean");
}
#Override
public E getBean()
{
if (validating)
{
return bean;
}
/// this call should always return null as setBean hasn't been
// called but we do this try to reduce the likelihood of this overload
// causing problems if someone accicentially uses this class
// when using setBean.
return super.getBean();
}
#Override
public BinderValidationStatus<E> validate()
{
try
{
// force getBean to return a bean during validation.
validating = true;
return super.validate();
}
finally
{
validating = false;
}
}
}```

Custom Exception Dispatcher in Spring Boot Application for response logic

I have a Spring Boot application that has the following approximate structure:
project
Api
ApiImpl
Application
Api is an interface that looks like this:
public interface Api {
public String methodOne(...) throws ExceptionOne, ExceptionTwo, ExceptionThree;
...
public int methodN(...) throws ExceptionOne, ExceptionThree, ExceptionFour;
}
ApiImpls is the request controller (in reality there is a second layer, but this should suffice for this example). There, I do something like the following right now:
#Controller
public class ApiImpl {
public String methodOne(...) {
try {
// do stuff that can yield an exception
}
catch(ExceptionOne e) {
// set proper response code and return values
}
catch(ExceptionTwo e) {
// set proper response code and return values
}
catch(ExceptionThree e) {
// set proper response code and return values
}
}
}
Basically, this behaviour yields a lot of repetition (might as well name my exceptions D, R, and Y...), but is otherwise very suited to handling the internal application logic.
My question is: How can I implement a custom Exception Dispatcher that would handle this in Java? Ideally, I would want something like this answer here, but unfortunately simply throwing the current exception like in that C++ code is not possible in Java, as far as I know. For brevity, what I would like to accomplish is something like the following:
#Controller
public class ApiImpl {
public String methodOne(...) {
try {
// do stuff that can yield an exception
}
catch(ExceptionOne e) {
handle()
}
}
private void handle() { // maybe Throwable or Exception subclass as parameter
// handle the correct exception type, set correct response code, etc.
}
}
Are there any good approaches to doing this so as to minimize code repetition?
Here is a preliminary attempt I tried to get this working:
public class Thrower {
public Thrower(int e) throws ExceptionOne, ExceptionTwo, ExceptionThree {
if(e == 0) {
throw new ExceptionOne();
}
if(e == 1) {
throw new ExceptionTwo();
}
if(e == 2) {
throw new ExceptionThree();
}
}
}
class ExceptionOne extends Exception {}
class ExceptionTwo extends Exception {}
class ExceptionThree extends Exception {}
public class ExceptionHandler {
private void handle(Exception ex) throws Exception {
try {
throw ex;
}
catch(ExceptionOne e) {
e.printStackTrace();
System.out.println("Exception one");
}
catch(ExceptionTwo e) {
e.printStackTrace();
System.out.println("Exception two");
}
catch(ExceptionThree e) {
e.printStackTrace();
System.out.println("Exception three");
}
}
public void causesException(int which) throws Throwable {
try {
Thrower t = new Thrower(which);
}
catch(Exception e) {
handle(e);
}
}
public static void main(String[] args) throws Throwable {
ExceptionHandler eh = new ExceptionHandler();
eh.causesException(0);
eh.causesException(1);
eh.causesException(2);
}
}
This works as expected, and I can handle the different exception types as needed (shown here using a constructor, but the principle would be the same). However, this feels extremely clunky.
If you are looking for globally handling all Controller Layer exceptions (in Spring MVC architecture), you can do that at one place for all controllers (option1 below) by using #ExceptionHandler methods which is a ControllerAdvice from Spring.
Option(1): Configure Exceptions in Separate Class
#ControllerAdvice
class MyProjectExceptionHandler {
#ExceptionHandler(value = ExceptionOne.class)
public R exceptionOne(ExceptionOne exe) {
//set proper response code and return values
}
#ExceptionHandler(value = ExceptionTwo.class)
public R exceptionTwo(ExceptionTwo exe) {
//set proper response code and return values
}
}
Option(2): Configure Exceptions in Controller Class itself
If you are looking for handling the exceptions within the Controller class itself, then you can do that as below:
#Controller
public class ApiImpl {
public String methodOne(...) {
}
#ExceptionHandler(ExceptionOne.class)
public R exceptionTwo(ExceptionOne exe) {
//set proper response code and return values
}
//other exceptions
}
You can look more on this at here

How to handle exception thrown from ExceptionHandler in controller with ExceptionHandler in ControllerAdvice?

I have custom Exceptions extending Exception (MyException1, MyException2, MyException3)
#Controller
public class MyController {
/*
Method throwing MyException1
Method throwing MyException2
Method throwing MyException3
*/
#ExceptionHandler(MyException1.class)
public void handleMyException1(Exception ex){
//Do something
throw ex;
}
#ExceptionHandler(MyException2.class)
public void handleMyException2(Exception ex){
System.out.println("Exception Logged inside Controller")
}
}
#ControllerAdvice
public class MyGlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public void handleAllException(Exception ex){
System.out.println("Exception logged Outside Controller");
}
}
My Intention: To log MyException1 from controller advice
To log MyException2 inside handler in controller itself
To log MyException3 from controller advice
MyException2 and MyException3 are working as intended but MyException1 fails with "Failed to invoke #ExceptionHandler method .....handleMyException1"
You can pick one of the following options for your exception handling:
Option(1) : Remove #ExceptionHandler(MyException1.class) method from Controller so that it will be automatically handled by MyGlobalExceptionHandler.
Option(2) : Create MyException4 (which is a Wrapper for MyException1 with added information) & throw it from Controller as shown below:
#Controller
public class MyController {
/*
Method throwing MyException1
Method throwing MyException2
Method throwing MyException3
*/
#ExceptionHandler(MyException1.class)
public void handleMyException1(Exception ex){
//MyException4 exe4 = new MyException4();
// Add the required details to it
throw exe4;
}
#ExceptionHandler(MyException2.class)
public void handleMyException2(Exception ex){
System.out.println("Exception Logged inside Controller")
}
}
#ControllerAdvice
public class MyGlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public void handleAllException(Exception ex){
System.out.println("Exception logged Outside Controller");
}
}
P.S.: I did not add Option(3) here, which is manually invoking MyGlobalExceptionHandler's handleAllException() as It is not a good practice. Rather you should simply throw the exception and the #ExceptionHandler will take care automatically.
One more problem with the manual invocation is that at some point of time in future, it will be problematic to debug the exceptions as some of your flows manually call MyGlobalExceptionHandler and some flows are called by the framework.

Spring State Machine Error Handling not working

I did all the setup for error handling
#PostConstruct
public void addStateMachineInterceptor() {
stateMachine.getStateMachineAccessor().withRegion().addStateMachineInterceptor(interceptor);
stateMachine.getStateMachineAccessor().doWithRegion(errorinterceptor);
}
created interceptor to handle error:
#Service
public class OrderStateMachineFunction<T> implements StateMachineFunction<StateMachineAccess<String, String>> {
#Override
public void apply(StateMachineAccess<String, String> stringStringStateMachineAccess) {
stringStringStateMachineAccess.addStateMachineInterceptor(
new StateMachineInterceptorAdapter<String, String>() {
#Override
public Exception stateMachineError(StateMachine<String, String> stateMachine,
Exception exception) {
// return null indicating handled error
return exception;
}
});
}
}
But I can't see the call going into OrderStateMachineFunction, when we throw the exception from the action.
And after that state machine behave some wired way, like it stops calling preStateChange method after this.stateMachine.sendEvent(eventData);. It seems state machine breaks down after you throw the exception from the action.
#Service
public class OrderStateMachineInterceptor extends StateMachineInterceptorAdapter {
#Override
public void preStateChange(State newState, Message message, Transition transition, StateMachine stateMachine) {
System.out.println("Manish");
}
}
After trying few bit, I have seen that if I comment the resetStateMachine, it works as expected, but without that I am not able to inform the currentstate to state machine:
public boolean fireEvent(Object data, String previousState, String event) {
Message<String> eventData = MessageBuilder.withPayload(event)
.setHeader(DATA_KEY, data)
.build();
this.stateMachine.stop();
// this.stateMachine
// .getStateMachineAccessor()
// .withRegion()
// .resetStateMachine(new DefaultStateMachineContext(previousState, event, eventData.getHeaders(), null));
this.stateMachine.start();
return this.stateMachine.sendEvent(eventData);
}
Not sure if you still need this. But I bumped into similar issue. I wanted to propagate exception from state machine to the caller. I implemented StateMachineInterceptor. And inside the state machine transition functions I am setting:
try
{
..
}
catch (WhateverException e)
{
stateMachine.setStateMachineError(e);
throw e;
}
Then inside the interceptor's stateMachineError method, I have added the Exception in the extendedState map:
public Exception stateMachineError(StateMachine<States, Events> stateMachine, Exception exception)
{
stateMachine.getExtendedState().getVariables().put("ERROR", exception);
logger.error("StateMachineError", exception);
return exception;
}
Inside resetStateMachine I have added the interceptor to the statemachine.
a.addStateMachineInterceptor(new LoggingStateMachineInterceptor());
Then when I am calling the sendEvent method, I am doing this:
if (stateMachine.hasStateMachineError())
{
throw (Exception) svtStateMachine.getExtendedState().getVariables().get("ERROR");
}
This is returning the WhateverException right to the caller. Which in my case is a RestController.
The approach I'm taking here is combining the extended state to store errors with an error action.
If an expected exception happens in your action and any class inside of it, I include it in the extended state context
context.getExtendedState().getVariables().put("error", MyBussinessException);
then, on my error action (configured like this)
.withExternal()
.source(State.INIT)
.target(State.STARTED)
.action(action, errorAction)
.event(Events.INIT)
Outside machine context, I always check if that field is present or not, and translate it to proper response code.
If any exception is thrown from action, error action will be triggered. There you can check known errors (and let them bubble up), or include a new errors (if that was unexpected)
public class ErrorAction implements Action<States, Events> {
#Override
public void execute(StateContext<States, Events> context) {
if(!context.getExtendedState().getVariables().containsKey("error")
context.getExtendedState().getVariables().put("error", new GenericException());
}
}

Catching the IllegalArgumentException thrown by PropertyEditors in Spring

I have a PropertyEditor in order to translate ids into Persons, with it's setAsText (String text) as follows:
public void setAsText(String text) throws IllegalArgumentException {
try {
int id = Integer.parseInt(text);
Person person = peopleService.get(id);
this.setValue(person);
}
catch (NumberFormatException ex) {
// ...
throw new IllegalArgumentException("Not a number!: " + text);
}
catch (PersonNotFoundExcetion ex) {
// ...
throw new IllegalArgumentException("Impossible to get Person: " + text);
}
}
And my PeopleController has a method as follows:
#RequestMapping("/getPerson")
public void ver (#RequestParam Person person, Model model) {
model.addAttribute (person);
// ...
}
I want to catch the IllegalArgumentException in order to show a friendly message to the user, such as "Sorry, the Person you are looking for isn't here", but I don't know where to do that...
Thanks!
General exception handling can be done in this way:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAllExceptions(Exception e) {
return "redirect:/error.html"; /* use the correct view name */
}
More specfic you could use BindingResult
#RequestMapping(value = "/datedata", method = RequestMethod.POST)
public String create(
#ModelAttribute("datedata") final DateData datedata,
final BindingResult result) {
if (result.hasErrors()) {
return "datedata/create";
} else {
...
return "myView";
}
}
But I guess this works only for "Forms" (ModelAttribute)
In my humble opinion it is not a good idea to let Spring handle validaten of user input by property editors. I would strongly recommend to use the Form way: Build a command object with a STRING field an use a validator on it.
The exception ought to be caught in the Controller. It should never leak out to the view and end user.
If this is a web app, I'd recommend using the validation and binding API rather than PropertyEditor. That will allow you to return Errors that you can use to tell the UI what needs to be corrected.
Your exception handling needs work. I would not recommend catching an exception and doing nothing other than wrapping it and re-throwing. That's not handling anything or adding new information. It's actually less information as coded.

Categories

Resources