JsfExceptionHandler.java

// license-header java merge-point
// Generated by andromda-jsf cartridge (exception\JsfExceptionHandler.java.vsl) DO NOT EDIT!
package org.andromda.samples.onlinestore;

import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.application.NavigationHandler;
import javax.faces.application.ViewExpiredException;
import javax.faces.component.UINamingContainer;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;

class JsfExceptionHandler
    extends ExceptionHandlerWrapper 
{
    /**
     * Parent ExceptionHandler
     */
    private ExceptionHandler parent;

    /**
     * Constructor
     *
     * @param parent the parent ExceptionHandler
     */
    public JsfExceptionHandler(ExceptionHandler parent) 
    {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getWrapped() 
    {
        return this.parent;
    }

    /**
     * Append a message to the set of messages associated with the specified 
     * client identifier, if clientId is not null. If clientId is null, this
     * message is assumed to not be associated with any specific component instance
     *
     * @param clientId the client identifier with which this message is associated (if any)
     * @param message the message to be appended
     * @param severity the severity of the message
     */
    private void addFacesMessage(final String clientId, 
                                 final String message,
                                 final FacesMessage.Severity severity)
    {
        FacesContext.getCurrentInstance().addMessage(clientId, 
                        new FacesMessage(severity,message,null));
    }

    /**
     * Append a message to the current instance of FacesContext
     *
     * @param message the message to be appended
     * @param severity the severity of the message
     */    
    private void addFacesMessage(final String message, final FacesMessage.Severity severity)
    {
        addFacesMessage(null,message,severity);
    }

    /**
     * Append an error message to the set of messages associated with the 
     * specified client identifier, if clientId is not null. If clientId is 
     * null, this error message is assumed to not be associated with any 
     * specific component instance
     *
     * @param clientId the client identifier with which this message is associated (if any)
     * @param message the message to be appended
     */
    private void addErrorMessage(String clientId, String message)
    {
        addFacesMessage(clientId, message, FacesMessage.SEVERITY_ERROR);
    }
    
    /**
     * Append an error message to current instance of faces context
     *
     * @param message the message to be appended
     */    
    private void addErrorMessage(String message)
    {
        addErrorMessage(null,message);
    }
    
    /**
     * Show validation messages of a given severity
     *
     * @param validationMessages Collection of ValidationExceptionInfo
     * @param severity the severity of the validation messages
     * @return true if some clientId has any attached message
     */    
    private boolean showValidationMessages(final Collection<ValidationException.ValidationExceptionInfo> validationMessages, 
                                           final FacesMessage.Severity severity)
    {
        boolean existsFieldMessage=false;
        
        for(ValidationException.ValidationExceptionInfo validationMessage: validationMessages){
            String fieldName=validationMessage.getFieldName();
            if(fieldName == null){
                addFacesMessage(Messages.get(validationMessage.getMessage(),validationMessage.getArgs()),severity);
            } 
            else 
            {
                final FacesContext facesContext = FacesContext.getCurrentInstance();
                final char namingSeparator=UINamingContainer.getSeparatorChar(facesContext); 
                if(fieldName.indexOf(namingSeparator) > -1)
                {
                    //is clientId
                    addFacesMessage(fieldName,Messages.get(validationMessage.getMessage(),validationMessage.getArgs()),severity);
                    existsFieldMessage=true;
                }
                else
                {
                    //changing from complex.field to complexField
                    final String[] nameParts=StringUtils.split(fieldName,'.');
                    for(int c=1; c<nameParts.length; c++)
                    {
                        nameParts[c]=StringUtils.capitalize(nameParts[c]);
                    }
                    fieldName=StringUtils.join(nameParts);

                    //creates the clientId using the last posted form
                    final String clientId=lastPostedForm()+namingSeparator+fieldName;
                    if(facesContext.getViewRoot().findComponent(clientId) == null)
                    {
                        //didn't find the component, try with fieldName as clientId
                        if(facesContext.getViewRoot().findComponent(fieldName) == null)
                        {
                            //didn't find the component, put it as a general message
                            addFacesMessage(Messages.get(validationMessage.getMessage(),validationMessage.getArgs()),severity);
                        }
                        else
                        {
                            addFacesMessage(fieldName,Messages.get(validationMessage.getMessage(),validationMessage.getArgs()),severity);
                            existsFieldMessage=true;
                        }
                    }
                    else
                    {
                        addFacesMessage(clientId,Messages.get(validationMessage.getMessage(),validationMessage.getArgs()),severity);
                        existsFieldMessage=true;
                    }
                }
            }
        }
        return existsFieldMessage;
    }

    private static final Pattern compiledPattern = Pattern.compile("(.*)(\\{\\s*([\\w|\\.+]*)\\s*\\})(.*)");

    /**
     * The property on the exception which could possibly contain any 
     * arguments on the matched exception.
     */
    private static final String MESSAGE_ARGUMENTS = "messageArguments";

    /**
     * Attempts to retrieve any arguments from the exception from a property
     * named "messageArguments" on the exception.
     *
     * @param throwable the Exception containing the message to retrieve
     * @return the retrieved message arguments (if any) from the exception.
     */
    private Object[] getMessageArguments(final Throwable cause)
    {
        Object[] arguments = null;
        if (cause != null && cause.getMessage() != null)
        {
            if (PropertyUtils.isReadable(cause, MESSAGE_ARGUMENTS))
            {
                try {
                    arguments = (Object[])PropertyUtils.getProperty(cause, MESSAGE_ARGUMENTS);
                } catch (Exception e) {
                    //just ignore
                }
            }
        }
        return arguments;
    }

    /**
     * Handles Exceptions by retrieving the message and attempting to extract
     * the specified pattern defined within this class. If a string can not be
     * found matching the pattern, the exception is re-thrown
     *
     * @param throwable the Exception containing the message to retrieve
     * @return the retrieved string matching the pattern.
     */
    private boolean handleException(final Throwable cause)
    {
        String matched = null;
        if (cause != null && cause.getMessage() != null)
        {
            final Matcher matcher = compiledPattern.matcher(cause.getMessage().replaceAll("[\\s]+", " "));
            try
            {
                if (matcher.matches())
                {
                    matched = matcher.group(3);
                }
            }
            catch (IllegalStateException ex)
            {
                return false;
            }
            if (matched == null)
            {
                return false;
            }
            else
            {
                FacesContext.getCurrentInstance().addMessage(null, 
                    new FacesMessage(javax.faces.application.FacesMessage.SEVERITY_ERROR,
                        Messages.get(matched, getMessageArguments(cause)),null));
                return true;
            }
        }
        else
        {
            return false;
        }
    }

    /**
     * Attempts to retrieve the last posted form
     * 
     * @return the last posted form
     */
    private String lastPostedForm()
    {
        final Object value=FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get(ControllerBase.LAST_POSTED_FORM_CLIENT_ID);
        return value==null?StringUtils.EMPTY:value.toString();
    }

    @Override
    public void handle() throws FacesException 
    {
        boolean existsMessage=false;
        boolean existsFieldMessage=false;
        
        for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) 
        {
            final ExceptionQueuedEvent event = i.next();
            final ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
            final Throwable t = context.getException();
            if (t instanceof ViewExpiredException) 
            {
                final FacesContext fc = FacesContext.getCurrentInstance();
                final NavigationHandler nav = fc.getApplication().getNavigationHandler();
                try {
                    nav.handleNavigation(fc, null, "/login.jsf?faces-redirect=true");
                    fc.renderResponse();
                } finally {
                    i.remove();
                }
            }
            else
            {
                final Throwable cause = ExceptionUtils.getRootCause(t);
                if(cause instanceof ValidationException)
                {
                    final ValidationException vd = (ValidationException)cause;
                    existsFieldMessage=
                        showValidationMessages(vd.getErrors(),FacesMessage.SEVERITY_ERROR) ||
                        showValidationMessages(vd.getWarnings(),FacesMessage.SEVERITY_WARN);
                    i.remove();
                    existsMessage=true;
                }
                else if(handleException(cause))
                {
                    i.remove();
                    existsMessage=true;
                }
                else 
                {

                    Throwable exception = t; 
                    while(exception != null && !(exception instanceof SQLException))//DB exception ? 
                    {
                        exception = exception.getCause();
                    }
                    
                    String message;
                    if(exception instanceof SQLException)
                    {
                        message = Messages.get("attention", new String[]{exception.getMessage()});
                    } 
                    else 
                    {
                        final String excMessage = t.getMessage()==null?t.toString():t.getMessage();
                        message = Messages.get("unexpected.error", new String[]{excMessage});
                        Logger.getAnonymousLogger().severe(excMessage);
                    }
                    
                    JsfUtils.addErrorMessage(message);

                    i.remove();
                    existsMessage=true;                    
                }
            }
        }

        if(existsMessage)
        {
            FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("jsfMessagesTitle", Messages.get("errors.header"));
            if(existsFieldMessage)
            {
                addErrorMessage(Messages.get("there.are.field.validation.errors"));
            }
        }
        
        // At this point, the queue will not contain known exception.
        // Therefore, let the parent handle them.
        getWrapped().handle();
    }
}