Hiding exceptions in tooltip.

When using a BeanItem, setValue() can fail because of an error in the underlying setter method. Not all such errors can be prevented by validation in the user interface. When the application is in production and such an event happens,
only the error message
should show up, not the stack trace. I have found no way to intercept the error (should this matter, the example involves a value change event on a text field inside a grid layout). In the example below, the user-oriented message is at the top – that is all that should appear unless running in debug mode.


Is there a specific kind of exception I can raise that will not cause the stack trace to appear, or some setting that can be used in production mode
to hide stack traces (I have looked at AbstractField and the code seems rather hard coded to include the stack trace).

You can override Application.terminalError() to handle all such exceptions.

The default implementation just sets the received ErrorMessage or exception as the component error. For any exceptions that do not implement ErrorMessage, the stack trace is shown by default. You can convert such exceptions to e.g. UserError instances with the correct message.

SystemError also shows the stack trace in case you want to eliminate all stack traces from what the user might see. However, you should then make sure the errors are properly logged for later troubleshooting.

The problem is that when AbstractField.getMessage() is called, it not only displays the ComponentError that I set in terminalError, but also the private Buffered.SourceException currentBufferedSourceException that is has saved.

Since that variable is private, the field always displays the combination of my error and the original Exception. Since the variable is private, it is somewhat painful to override. I created a class CustomTextField, with an override to getMessage(). The last statement of the routine is modified as follows:

        // get rid of the private exception from AbstractField that is systematically
        // included (private Buffered.SourceException currentBufferedSourceException) 
        if (superError instanceof CompositeErrorMessage) {
        	ArrayList<ErrorMessage> newErrors = new ArrayList<ErrorMessage>();
        	final CompositeErrorMessage compositeError = (CompositeErrorMessage)superError;
        	for (Iterator iterator = compositeError.iterator(); iterator.hasNext();) {
        		ErrorMessage em = (ErrorMessage) iterator.next();
        		if (!(em instanceof Buffered.SourceException)) {
        			newErrors.add(em);
        		}
        	}
        	// our version of terminalError has already added a UserError in this case
        	// so there is always at least an error.
        	return new CompositeErrorMessage(newErrors);
        } else {
        	return new CompositeErrorMessage(new ErrorMessage[]{superError,validationError });
        }

This solution helped me in coming up with my own. Unfortunately, creating a custom field was not a viable solution for us. Instead I wrote a custom ComponentErrorHandler that clears out the currentBufferedSourceException. Then, assuming you are using a custom Field Factory, you can apply the component error handler to all your fields using the setErrorHandler method.


@Override
	public boolean handleComponentError(ComponentErrorEvent event) {
		if(event instanceof ChangeVariablesErrorEvent){
			Throwable cause = event.getThrowable() == null ? null 
					: event.getThrowable().getCause();
			if(cause != null && cause instanceof ConversionException){
				Component owner = ((ChangeVariablesErrorEvent)event).getComponent();
				if(owner instanceof AbstractComponent){
					AbstractComponent component = (AbstractComponent)owner;
					if(component instanceof AbstractField){
						AbstractField field = (AbstractField) component;
						if(debugLogger.isDebugEnabled()){
							debugLogger.debug("Removing Component Error Stack Trace from "
									+ "UI. Cause of exception:", field.getErrorMessage());
						}
						field.setCurrentBufferedSourceException(null);
					}
					component.setComponentError(new Validator.InvalidValueException(
							"Incorrect Format"));
					return true;
				}
			}
		}
		return false;
	}

Hope this helps.