Dashboard > Spring JSF Integration > Home > Configuring JSF Validators, Converters, and UIComponents in Spring
  Spring JSF Integration Log In View a printable version of the current page.  
  Configuring JSF Validators, Converters, and UIComponents in Spring
Added by Ken Weiner, last edited by Ken Weiner on Aug 29, 2005  (view change)
Labels: 
(None)

Validator, Converter, and UIComponent Integration Approach

Spring currently comes with the class DelegatingVariableResolver that makes it possible to configure JSF managed beans in the Spring application context. This document provides an approach for extending this delegation pattern to JSF validators, converters, and UI components so that you can manage them in Spring, taking full advantage of Spring's powerful IoC features.

Rick Hightower and I (Ken Weiner) worked together on a project this summer using the popular Spring/Hibernate/JSF technology stack. Rick came up with a way of configuring JSF validators, converters, and UI components in the Spring application context. The approach was to create a Spring version of javax.faces.application.Application called SpringApplication that wraps the existing Application and delegates to it in all methods except the factory methods that create validators, converters, and UI components. In the implementation of those factory methods, he first tries to find the object in the Spring application context, and then if it isn't found, delegates the creation to the original Application implementation. See Rick's Blog Entry on achieving DI with JSF Components.

Rick's method of configuring the SpringApplication was to write a servlet called ChangeApplicationContextServlet that switches out the Application implementation upon initialization as follows:

ChangeApplicationContextServlet.init()
ApplicationFactory appFactory = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
Application application = appFactory.getApplication();
// SpringService is a custom object that holds an instance of the application context
ApplicationContext applicationContext = SpringService.getApplicationContext();
SpringApplication springApplication = new SpringApplication(applicationContext);
springApplication.setApplication(application);
appFactory.setApplication(springApplication);

I prefer a different process for configuring the SpringApplication. It takes advantage of JSF's ability to configure custom implementations of the ApplicationFactory via faces-config.xml. When JSF sees that a custom ApplicationFactory implementation has been configured, it instantiates it, passing it an instance of the original ApplicationFactory implementation (as long as the appropriate constructor is provided). See the JSF Specification 10.3.5. The custom ApplicationFactory, SpringApplicationFactory, can then create and establish the SpringApplication implementation.

I recently reworked SpringApplication to behave more like DelegatingVariableResolver in that it checks the original JSF Application implementation first. Then it consults the root Spring application context if the JSF Application implementation isn't able to produce the validator, converter or UI component.

You'll notice that the Validators, Converters, and UIComponents can be configured in the application context with a bean name equal to their ID. In addition, the Converters associated with particular classes can be configured with a bean name equal to their fully-qualified package name and the suffix "-Converter".

Examples Spring bean definitions:

applicationContext.xml
<bean name="myValidator" class="org.example.MyValidator"/>
<bean name="myConverter" class="org.example.MyConverter"/>
<bean name="java.util.Locale-Converter" class="org.example.LocaleConverter"/>
<bean name="myComponent" class="org.example.MyComponent" singleton="false"/>

Example JSF tags:

example.jsp
...
<h:inputText id="myInput" value="myValue">
    <f:validator validatorId="myValidator"/>
    <f:converter converterId="myConverter"/>
</h:inputText>

<h:outputText id="myOutput" value="#{myBackingBean.aJavaUtilLocale}"/>
...

The result is the following code which I'd like to see added to the Spring project if it is useful for others. Please post comments to this page. This code has been tested using Apache MyFaces 1.0.9, but hasn't yet been tested on Sun's JSF RI. If you can test with the RI, that would be much appreciated.

faces-config.xml
<factory>
    <application-factory>org.springframework.web.jsf.SpringApplicationFactory</application-factory>
</factory>
SpringApplicationFactory
package org.springframework.web.jsf;

import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;

/**
 * Replaces original ApplicationFactory's Application with a SpringApplication
 * that wraps the orginal Application.
 * <p>
 * @author Ken Weiner
 * @see SpringApplication
 */
public class SpringApplicationFactory extends ApplicationFactory {
    
    private ApplicationFactory originalApplicationFactory;

    public SpringApplicationFactory() {
        // Default constructor
    }

    /**
     * Replaces the original ApplicationFactory's Application
     * with the SpringApplication.
     * @param applicationFactory the original ApplicationFactory
     */
    public SpringApplicationFactory(ApplicationFactory applicationFactory) {
        this.originalApplicationFactory = applicationFactory;
        Application originalApplication = applicationFactory.getApplication();
        setApplication(originalApplication);
    }

    /**
     * Delegates the retrieval of the Application to the
     * original ApplicationFactory.
     * @return the Application from the original application factory
     */
    public Application getApplication() {
        return this.originalApplicationFactory.getApplication();
    }

    /**
     * Sets the Application in the original ApplicationFactory
     * after wrapping it in a SpringApplication if it isn't already
     * an instance of one.
     */
    public void setApplication(Application application) {
        if (!(application instanceof SpringApplication)) {
            Application springApplication = new SpringApplication(application);
            this.originalApplicationFactory.setApplication(springApplication);
        } else {
            this.originalApplicationFactory.setApplication(application);
        }
    }
}
SpringApplication
package org.springframework.web.jsf;

import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;

import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.NavigationHandler;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.MethodBinding;
import javax.faces.el.PropertyResolver;
import javax.faces.el.ReferenceSyntaxException;
import javax.faces.el.ValueBinding;
import javax.faces.el.VariableResolver;
import javax.faces.event.ActionListener;
import javax.faces.validator.Validator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.jsf.FacesContextUtils;

/**
 * Has the ability to obtain Spring-managed Converters,
 * Validators, and UIComponents in the case that they are not 
 * configured in the JSF environment.
 * <p>
 * For all other behavior, this class delegates to the original
 * JSF Application implementation.
 * <p>
 * @author Rick Hightower
 * @author Ken Weiner
 */
public class SpringApplication extends Application {

    protected final Log logger = LogFactory.getLog(getClass());

    private Application originalApplication;

    public SpringApplication() {
        super();
    }

    public SpringApplication(Application originalApplication) {
        this.originalApplication = originalApplication;
    }

    public ActionListener getActionListener() {
        return this.originalApplication.getActionListener();
    }

    public void setActionListener(ActionListener listener) {
        this.originalApplication.setActionListener(listener);
    }

    public Locale getDefaultLocale() {
        return this.originalApplication.getDefaultLocale();
    }

    public void setDefaultLocale(Locale locale) {
        this.originalApplication.setDefaultLocale(locale);
    }

    public String getDefaultRenderKitId() {
        return this.originalApplication.getDefaultRenderKitId();
    }

    public void setDefaultRenderKitId(String renderKitId) {
        this.originalApplication.setDefaultRenderKitId(renderKitId);
    }

    public String getMessageBundle() {
        return this.originalApplication.getMessageBundle();
    }

    public void setMessageBundle(String bundle) {
        this.originalApplication.setMessageBundle(bundle);
    }

    public NavigationHandler getNavigationHandler() {
        return this.originalApplication.getNavigationHandler();
    }

    public void setNavigationHandler(NavigationHandler handler) {
        this.originalApplication.setNavigationHandler(handler);
    }

    public PropertyResolver getPropertyResolver() {
        return this.originalApplication.getPropertyResolver();
    }

    public void setPropertyResolver(PropertyResolver resolver) {
        this.originalApplication.setPropertyResolver(resolver);
    }

    public VariableResolver getVariableResolver() {
        return this.originalApplication.getVariableResolver();
    }

    public void setVariableResolver(VariableResolver resolver) {
        this.originalApplication.setVariableResolver(resolver);
    }

    public ViewHandler getViewHandler() {
        return this.originalApplication.getViewHandler();
    }

    public void setViewHandler(ViewHandler handler) {
        this.originalApplication.setViewHandler(handler);
    }

    public StateManager getStateManager() {
        return this.originalApplication.getStateManager();
    }

    public void setStateManager(StateManager manager) {
        this.originalApplication.setStateManager(manager);
    }

    public void addComponent(String componentType, String componentClass) {
        this.originalApplication.addComponent(componentType, componentClass);
    }

    /**
     * Tries to create a UI component via the original Application and looks for
     * the UI component in Spring's root application context if it is not
     * obtainable through JSF's Application.
     * @param componentType the component type and Spring bean name for the UIComponent
     * @return the resulting UIComponent
     *         through either the JSF Application or Spring Application Context.
     * @throws FacesException if the component could not be created or located in Spring
     */
    public UIComponent createComponent(String componentType) throws FacesException {
        FacesException originalException = null;
        try {
            // Create component with original application
            if (logger.isDebugEnabled()) {
                logger.debug("Attempting to create component with type '" + componentType + "' using original Application");
            }
            UIComponent originalComponent = this.originalApplication.createComponent(componentType);
            if (originalComponent != null) {
                return originalComponent;
            }
            originalException = new FacesException("Original Application returned a null component");
        } catch (FacesException e) {
            originalException = e;
        }
        
        // Get component from Spring root context
        if (logger.isDebugEnabled()) {
            logger.debug("Attempting to find component '" + componentType + "' in root WebApplicationContext");
        }
        FacesContext facesContext = FacesContext.getCurrentInstance();
        WebApplicationContext wac = getWebApplicationContext(facesContext);
        if (wac.containsBean(componentType)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Successfully found component '" + componentType + "' in root WebApplicationContext");
            }
            return (UIComponent) wac.getBean(componentType);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Could not create component '" + componentType + "'");
        }
        throw new FacesException("Could not create component using original Application.  " +
                                 "Also, could not find component in root WebApplicationContext", originalException);
    }

    /**
     * Tries to obtain a UI component though the given value binding.
     * If a non-null component is not obtained, then this method simply
     * delegates to {@link #createComponent(String)}, passing the componentType.
     * @param componentBinding the component value binding
     * @param context the FacesContext
     * @param componentType the component type
     * @return the resulting UIComponent
     *         through either the value binding, JSF Application or Spring Application Context.
     * @throws FacesException if the component could not be created or located in Spring
     */
    public UIComponent createComponent(ValueBinding componentBinding,
            FacesContext context, String componentType) throws FacesException {

        FacesException originalException = null;
        try {
            // Create component with value binding
            if (logger.isDebugEnabled()) {
                logger.debug("Attempting to create component with value binding");
            }
            Object value = componentBinding.getValue(context);
            if (value != null && value instanceof UIComponent) {
                return (UIComponent) value;
            }
            originalException = new FacesException("Original Application returned a null component");
        } catch (FacesException e) {
            originalException = e;
        }

        try {
            // Value binding did not return a UIComponent, so attempt creation by type
            return this.createComponent(componentType);
        } catch (FacesException e) {
            throw new FacesException(originalException.getMessage() + 
                                     " Additionally, the component could not be created based on the component type", e);
        }
    }

    public Iterator getComponentTypes() {
        return this.originalApplication.getComponentTypes();
    }

    public void addConverter(String converterId, String converterClass) {
        this.originalApplication.addConverter(converterId, converterClass);
    }

    public void addConverter(Class targetClass, String converterClass) {
        this.originalApplication.addConverter(targetClass, converterClass);
    }

    /**
     * Tries to create converter via the original Application and looks for
     * the converter in Spring's root application context if it is not
     * obtainable through JSF's Application.
     * @param converterId the converter ID and Spring bean name for the Converter
     * @return the resulting Converter
     * @throws FacesException if the converter could not be created or located in Spring
     */
    public Converter createConverter(String converterId) throws FacesException {
        FacesException originalException = null;
        try {
            // Create converter with original application
            if (logger.isDebugEnabled()) {
                logger.debug("Attempting to create converter with id '" + converterId + "' using original Application");
            }
            Converter originalConverter = this.originalApplication.createConverter(converterId);
            if (originalConverter != null) {
                return originalConverter;
            }
            originalException = new FacesException("Original Application returned a null Converter");
        } catch (FacesException e) {
            originalException = e;
        }
        
        // Get converter from Spring root context
        if (logger.isDebugEnabled()) {
            logger.debug("Attempting to find converter '" + converterId + "' in root WebApplicationContext");
        }
        FacesContext facesContext = FacesContext.getCurrentInstance();
        WebApplicationContext wac = getWebApplicationContext(facesContext);
        if (wac.containsBean(converterId)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Successfully found converter '" + converterId + "' in root WebApplicationContext");
            }
            return (Converter) wac.getBean(converterId);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Could not create converter '" + converterId + "'");
        }
        throw new FacesException("Could not create converter using original Application.  " +
                                 "Also, could not find converter in root WebApplicationContext", originalException);
    }

    /**
     * Tries to create converter via the original Application and looks for
     * the converter in Spring's root application context if it is not
     * obtainable through JSF's Application.
     * <p>
     * When looking for the converter in Spring, the bean name will be a
     * concatenation of the fully-qualified class name of the targetClass with
     * the suffix <code>-Converter</code>.  For example, if the target class is
     * <code>java.util.Locale</code>, then the corresponding converter would need
     * to have the bean name <code>java.util.Locale-Converter</code>.
     * @param targetClass the class on which the converter operates
     * @return the resulting Converter
     * @throws FacesException if the converter could not be created or located in Spring
     */
    public Converter createConverter(Class targetClass) throws FacesException {
        FacesException originalException = null;
        try {
            // Create converter with original application
            if (logger.isDebugEnabled()) {
                logger.debug("Attempting to create converter for class '" + targetClass.getName() + "' using original Application");
            }
            Converter originalConverter = this.originalApplication.createConverter(targetClass);
            if (originalConverter != null) {
                return originalConverter;
            }
            originalException = new FacesException("Original Application returned a null Converter");
        } catch (FacesException e) {
            originalException = e;
        }
        
        // Get converter from Spring root context
        String converterBeanName = targetClass.getName() + "-Converter";
        if (logger.isDebugEnabled()) {
            logger.debug("Attempting to find converter '" + converterBeanName + "' in root WebApplicationContext");
        }
        FacesContext facesContext = FacesContext.getCurrentInstance();
        WebApplicationContext wac = getWebApplicationContext(facesContext);
        if (wac.containsBean(converterBeanName)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Successfully found converter '" + converterBeanName + "' in root WebApplicationContext");
            }
            return (Converter) wac.getBean(converterBeanName);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Could not create converter for class '" + targetClass.getName() + "'");
        }
        throw new FacesException("Could not create converter using original Application.  " +
                                 "Also, could not find converter in root WebApplicationContext", originalException);
    }

    public Iterator getConverterIds() {
        return this.originalApplication.getConverterIds();
    }

    public Iterator getConverterTypes() {
        return this.originalApplication.getConverterTypes();
    }

    public MethodBinding createMethodBinding(String ref, Class[] params)
            throws ReferenceSyntaxException {
        return this.originalApplication.createMethodBinding(ref, params);
    }

    public Iterator getSupportedLocales() {
        return this.originalApplication.getSupportedLocales();
    }

    public void setSupportedLocales(Collection locales) {
        this.originalApplication.setSupportedLocales(locales);
    }

    public void addValidator(String validatorId, String validatorClass) {
        this.originalApplication.addValidator(validatorId, validatorClass);
    }

    /**
     * Tries to create validator via the original Application and looks for
     * the validator in Spring's root application context if it is not
     * obtainable through JSF's Application.
     * @param validatorId the validator ID and Spring bean name for the Validator
     * @return the resulting Validator
     * @throws FacesException if the validator could not be created or located in Spring
     */
    public Validator createValidator(String validatorId) throws FacesException {
        FacesException originalException = null;
        try {
            // Create validator with original application
            if (logger.isDebugEnabled()) {
                logger.debug("Attempting to create validator with id '" + validatorId + "' using original Application");
            }
            Validator originalValidator = this.originalApplication.createValidator(validatorId);
            if (originalValidator != null) {
                return originalValidator;
            }
            originalException = new FacesException("Original Application returned a null Validator");
        } catch (FacesException e) {
            originalException = e;
        }
        
        // Get validator from Spring root context
        if (logger.isDebugEnabled()) {
            logger.debug("Attempting to find validator '" + validatorId + "' in root WebApplicationContext");
        }
        FacesContext facesContext = FacesContext.getCurrentInstance();
        WebApplicationContext wac = getWebApplicationContext(facesContext);
        if (wac.containsBean(validatorId)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Successfully found validator '" + validatorId + "' in root WebApplicationContext");
            }
            return (Validator) wac.getBean(validatorId);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Could not create validator '" + validatorId + "'");
        }
        throw new FacesException("Could not create validator using original Application.  " + 
                                 "Also, could not find validator in root WebApplicationContext", originalException);
    }

    public Iterator getValidatorIds() {
        return this.originalApplication.getValidatorIds();
    }

    public ValueBinding createValueBinding(String ref)
            throws ReferenceSyntaxException {
        return this.originalApplication.createValueBinding(ref);
    }

    /**
     * Retrieve the web application context to delegate bean name resolution to.
     * Default implementation delegates to FacesContextUtils.
     * @param facesContext the current JSF context
     * @return the Spring web application context
     * @see FacesContextUtils#getRequiredWebApplicationContext
     */
    protected WebApplicationContext getWebApplicationContext(FacesContext facesContext) {
        return FacesContextUtils.getRequiredWebApplicationContext(facesContext);
    }
}

Site running on a free Atlassian Confluence Open Source Project License granted to Spring Framework. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators