You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@myfaces.apache.org by "ir.ing.Jan Dockx" <ja...@mac.com> on 2005/07/13 00:49:30 UTC

Exceptions from backing properties

In our code, the business objects properties setters do validation and 
throw exceptions (SemanticException). We don't want to repeat this 
validation. Our intention is to have the UIInput call the setter. If 
the setter throws an exception, it should be caught, and it's localized 
message should become a FacesMessage.

Now, the MyFaces UIInput code for updateModel is this:

     public void updateModel(FacesContext context)
     {
         if (!isValid()) return;
         if (!isLocalValueSet()) return;
         ValueBinding vb = getValueBinding("value");
         if (vb == null) return;
         try
         {
             vb.setValue(context, getLocalValue());
             setValue(null);
             setLocalValueSet(false);
         }
         catch (RuntimeException e)
         {
         	//Object[] args = {getId()};
             context.getExternalContext().log(e.getMessage(), e);
             _MessageUtils.addErrorMessage(context, 
this,CONVERSION_MESSAGE_ID,new Object[]{getId()});
             setValid(false);
         }
     }

As you see, a RuntimeException is caught. Now, the default value 
binding does catch the SemanticException, and wraps it in a 
EvaluationException, which is a RuntimeException, so this is caught 
here.

We can create a new ValueBinding implementation, and configure JSF to 
use it via faces-config. We did, and what our implementation does, is 
catch the SemanticException, and wrap it in a special 
EvaluationException, whose getMessage(), getLocalMessage() and toString 
returns the wrapped SemanticException's getLocalizedMessage(). This 
works in the Sun RI, but in MyFaces, as you can see, the message get's 
written to the log, and the message that is displayed is a application 
default CONVERSION_MESSAGE (which isn't really applicable either, BTW).

Point 1 I want to make is that JSF is really underspecified here. As 
far as I can see, the spec does not say clearly what to do with 
exceptions from the setters. Hence the different implementations. If 
there are any spec leads listening: consider introducing a configurable 
component for this, so people can plug in their own exception handling.

Point 2 is that we do not see any solution anymore. Another thing we 
tried is to create yet another ValueBinding implementation, that 
catches the SemanticException, and that adds its localized message to 
the FacesMessages there already, and calls component.setValid(false). 
Problem is, that after the above code, we loose all references to the 
component. The "vb.setValue(context, getLocalValue())" doesn't pass in 
the component. So in the value binding, we cannot set the component's 
valid property, and we cannot even use the component id to link the 
FacesMessage to the component.

We believe we're stuck, apart from forking the entire MyFaces project, 
and changing the code above.

If nobody sees a real solution for this, we suggest that the standard 
changes at some time, and that MyFaces takes the lead. We see 3 needs:
1) The JSF spec should define clearly what should happen to model 
exceptions.
2) This definition should say that is is configurable, and what the 
default is.
3) The default should be to show the exceptions localized message.
and/or
4) The ValueBinding.setValue(FacesContext, Object) method should get an 
extra parameter, the component: ValueBinding.setValue(FacesContext, 
UIComponent, Object).


The following code should make clear how this could be made 
configurable. We suppose the standard says that the 
ValueBinding.setValue(...) method needs
to wrap a model exception in a ModelException > FacesException > 
RuntimeException, and that there is a configurable model exception 
handler.

public interface ModelExceptionHandler {

	handleException(FacesContext context, UIComponent component, Throwable 
t) throws FacesException;

}

     public void updateModel(FacesContext context)
     {
         if (!isValid()) return;
         if (!isLocalValueSet()) return;
         ValueBinding vb = getValueBinding("value");
         if (vb == null) return;
         try  {
           try {
             vb.setValue(context, getLocalValue());
             setValue(null);
             setLocalValueSet(false);
           }
           catch (ModelException mExc) {
             modelExceptionHandler meh = 
context.getModelExceptionHandler();
             meh.handleException(context, this, mExc.getCause());
            }
         }
         catch (RuntimeException e)
         {
         	//Object[] args = {getId()};
             context.getExternalContext().log(e.getMessage(), e);
             _MessageUtils.addErrorMessage(context, 
this,CONVERSION_MESSAGE_ID,new Object[]{getId()});
             setValid(false);
         }
     }