Reputation: 19905
I have a component with a custom model (extending the wicket standard Model class). My model loads the data from a database/web service when Wicket calls getObject()
.
This lookup can fail for several reasons. I'd like to handle this error by displaying a nice message on the web page with the component. What is the best way to do that?
public class MyCustomModel extends Model {
@Override
public String getObject() {
try {
return Order.lookupOrderDataFromRemoteService();
} catch (Exception e) {
logger.error("Failed silently...");
// How do I propagate this to the component/page?
}
return null;
}
Note that the error happens inside the Model which is decoupled from the components.
Upvotes: 4
Views: 4660
Reputation: 1266
Handling an exception that happens in the model's getObject() is tricky, since by this time we are usually deep in the response phase of the whole request cycle, and it is too late to change the component hierarchy. So the only place to handle the exception is very much non-local, not anywhere near your component or model, but in the RequestCycle
.
There is a way around that though. We use a combination of a Behavior
and an IRequestCycleListener
to deal with this:
IRequestCycleListener#onException
allows you to examine any exception that was thrown during the request. If you return an IRequestHandler
from this method, that handler will be run and rendered instead of whatever else was going on beforehand.
We use this on its own to catch generic stuff like Hibernate's StaleObjectException
to redirect the user to a generic "someone else modified your object" page. If you
For more specific cases we add a RuntimeExceptionHandler
behavior:
public abstract class RuntimeExceptionHandler extends Behavior {
public abstract IRequestHandler handleRuntimeException(Component component, Exception ex);
}
In IRequestCycleListener
we walk through the current page's component tree to see whether any component has an instance of RuntimeExceptionHandler
. If we find one, we call its handleRuntimeException
method, and if it returns an IRequestHandler
that's the one we will use. This way you can have the actual handling of the error local to your page.
Example:
public MyPage() {
...
this.add(new RuntimeExceptionHandler() {
@Override public IRequestHandler handleRuntimeException(Component component, Exception ex) {
if (ex instanceof MySpecialException) {
// just an example, you really can do anything you want here.
// show a feedback message...
MyPage.this.error("something went wrong");
// then hide the affected component(s) so the error doesn't happen again...
myComponentWithErrorInModel.setVisible(false); // ...
// ...then finally just re-render this page:
return new RenderPageRequestHandler(new PageProvider(MyPage.this));
} else {
return null;
}
}
});
}
Note: This is not something shipped with Wicket, we rolled our own. We simply combined the IRequestCycleListener
and Behavior
features of Wicket to come up with this.
Upvotes: 7
Reputation: 5681
Your model could implement IComponentAssignedModel, thus being able to get hold on the owning component.
But I wonder how often are you able to reuse MyCustomModel? I know that some devs advocate creating standalone model implementations (often in separate packages). While there are general cases where this is useful (e.g. FeedbackMessagesModel), in my experience its easier to just create inner classes which are component specific.
Upvotes: 6
Reputation: 3121
You might want to simply return null in getObject
, and add logic to the controller class to display a message if getObject
returns null.
If you need custom messages for different fail reasons, you could add a property like String errorMessage;
to the model which is set when catching the Exception in getObject
- so your controller class can do something like this
if(model.getObject == null) {
add(new Label("label",model.getErrorMessage()));
} else {
/* display your model object*/
}
Upvotes: 0
Reputation: 27880
Being the main issue here that Model
s are by design decoupled from the component hierarchy, you could implement a component-aware Model
that will report all errors against a specific component.
Remember to make sure it implements Detachable
so that the related Component
will be detached.
If the Model
will perform an expensive operation, you might be interested in using LoadableDetachableModel
instead (take into account that Model.getObject()
might be called multiple times).
public class MyComponentAwareModel extends LoadableDetachableModel {
private Component comp;
public MyComponentAwareModel(Component comp) {
this.comp = comp;
}
protected Object load() {
try {
return Order.lookupOrderDataFromRemoteService();
} catch (Exception e) {
logger.error("Failed silently...");
comp.error("This is an error message");
}
return null;
}
protected void onDetach(){
comp.detach();
}
}
It might also be worth to take a try at Session.get().error())
instead.
Upvotes: 2
Reputation: 7696
I would add a FeedbackPanel to the page and call error("some description") in the catch clause.
Upvotes: 0