Friday, October 30, 2009

How JSF and ICEfaces Are Initialized in Application Startup

A Case for Study

We use a sample application called fooApp in the following analysis. The application is deployed to the server fooAdminServer in the domain fooDomain.

The log file when the application is started has the following log messages in that order:
2009-06-16 13:44:23,403 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/3pj2tv/APP-INF/lib/jsf-impl-1.1.jar!/com/sun/faces/jsf-ri-runtime.xml)
2009-06-16 13:44:23,512 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/qw1mk0/war/WEB-INF/lib/icefaces-comps-1.6.2.jar!/META-INF/faces-config.xml)
2009-06-16 13:44:23,544 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/3pj2tv/APP-INF/lib/icefaces-comps-1.6.2.jar!/META-INF/faces-config.xml)
2009-06-16 13:44:23,591 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/3pj2tv/APP-INF/lib/icefaces-1.6.2.jar!/META-INF/faces-config.xml)
2009-06-16 13:44:23,606 DEBUG [ConfigureListener]: parse(file:/C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/upload/fooApp/app/fooApp/fooApp/WEB-INF/faces-config.xml)
Note that each log message shows an xml file is parsed. There are other log messages in between these log messages. Those log messages basically shows the content in the xml files are loaded item by item.

These log messages are generated by the class com.sun.faces.config.ConfigureListener.

The method is
public void contextInitialized(ServletContextEvent sce)

The following is the copy of the function stack from Eclipse. I set a breakpoint on the constructor of D2DViewHandler.java.
(Note:image omitted here.)
The method basically is the following (decompiled from jar. May not be accurate):
public void contextInitialized(ServletContextEvent sce)
  {
    Digester digester = null;
    FacesConfigBean fcb = new FacesConfigBean();
    ServletContext context = sce.getServletContext();
    tlsExternalContext.set(new ServletContextAdapter(context));
    try
    {
      if (RIConstants.IS_UNIT_TEST_MODE) context
          .setAttribute(FACES_CONFIG_BEAN_KEY, fcb);
    }
    catch (Exception e)
    {
      if (log.isDebugEnabled()) log.debug("Can't query for test environment");
    }
    RIConstants.HTML_TLV_ACTIVE = isFeatureEnabled(context,
        "com.sun.faces.enableHtmlTagLibValidator");
    URL url = null;
    if (log.isDebugEnabled()) log.debug("contextInitialized("
        + context.getServletContextName() + ")");
    if (initialized()) return;
    digester = digester(isFeatureEnabled(context, "com.sun.faces.validateXml"));
    url = Util.getCurrentLoader(this)
        .getResource("com/sun/faces/jsf-ri-runtime.xml");
    parse(digester, url, fcb);
    Iterator resources;
    try
    {
      List list = new LinkedList();
      for (Enumeration items = Util.getCurrentLoader(this)
          .getResources("META-INF/faces-config.xml"); items.hasMoreElements(); list
          .add(0, items.nextElement()));
      resources = list.iterator();
    }
    catch (IOException e)
    {
      // omitted
    }
    for (; resources.hasNext(); parse(digester, url, fcb))
      url = (URL) resources.next();

    String paths = context.getInitParameter("javax.faces.CONFIG_FILES");
    if (paths != null)
    {
      for (StringTokenizer t = new StringTokenizer(paths.trim(), ","); t
          .hasMoreTokens();)
      {
        url = getContextURLForPath(context, t.nextToken().trim());
        if (url != null) parse(digester, url, fcb);
      }

    }
    url = getContextURLForPath(context, "/WEB-INF/faces-config.xml");
    if (url != null) parse(digester, url, fcb);
    try
    {
      configure(context, fcb);
    }
    catch (FacesException e)
    {
      e.printStackTrace();
      throw e;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new FacesException(e);
    }
    verifyFactories();
    if (isFeatureEnabled(context, "com.sun.faces.verifyObjects")) verifyObjects(context,
        fcb);
    tlsExternalContext.set(null);
  }
So it first reads the file com/sun/faces/jsf-ri-runtime.xml. Then it reads all the files META-INF/faces-config.xml on the classpath. Notice that there may be more than one META-INF/faces-config.xml file under different folders in the classpath. This is exactly how the ICEFaces configuration is loaded into the applicaiton. The application has the icefaces-1.6.2.jar on its classpath APP-INF/lib. And the jar contains a file META-INF/faces-config.xml. Also notice that at the beginning of the method, it creates a brand new object

FacesConfigBean fcb = new FacesConfigBean();

When it reads the xml files, it creates objects configured in those xml files and put them into this object.

The method calls configure(context, fcb) near the end. This method is the following( from decompiling):
protected void configure(ServletContext context, FacesConfigBean config)
      throws Exception
  {
    configure(config.getFactory());
    configure(config.getLifecycle());
    configure(config.getApplication());
    configure(config.getComponents());
    configure(config.getConvertersByClass());
    configure(config.getConvertersById());
    configure(config.getManagedBeans());
    configure(config.getNavigationRules());
    configure(config.getRenderKits());
    configure(config.getValidators());
  }
One thing to concern is that how multiple viewhandlers are setup. We have the following view handlers from various xml files in order:
  • com.sun.faces.application.ViewHandlerImpl in isf-ri-runtime.xml
  • com.icesoft.faces.application.D2DViewHandler in icefaces-1.6.2.jar faces-config.xml
  • com.myCompany.CustomViewHandler in /WEB-INF/faces-config.xml.
Do they all work at runtime, or one will override all the others? The method configure(config.getApplication()) is the following:

private void configure(ApplicationBean config) throws Exception
  {
    if (config == null) return;
    Application application = application();
    configure(config.getLocaleConfig());

    // omitted many lines here

    values = config.getViewHandlers();
    if (values != null && values.length > 0)
    {
      for (int i = 0; i < values.length; i++)
      {
        if (log.isTraceEnabled()) log
            .trace("setViewHandler(" + values[i] + ')');
        Object instance = Util.createInstance(values[i],
            javax.faces.application.ViewHandler.class,
            application.getViewHandler());
        if (instance != null) application
            .setViewHandler((ViewHandler) instance);
      }
    }
  }
Here the Application class is javax.faces.application.Application . The instance application is created by the method application():
private Application application()
  {
    ApplicationFactory afactory = (ApplicationFactory) FactoryFinder
        .getFactory("javax.faces.application.ApplicationFactory");
    return afactory.getApplication();
  }
From the log message, the application seems to be com.sun.faces.application.ApplicationImpl. And the following is its setViewHandler method:
public void setViewHandler(ViewHandler handler)
  {
    if (handler == null)
    {
      String message = Util
          .getExceptionMessageString("com.sun.faces.NULL_PARAMETERS_ERROR");
      message = message + " ViewHandler " + handler;
      throw new NullPointerException(message);
    }
    synchronized (this)
    {
      if (associate.isResponseRendered())
      {
        if (log.isErrorEnabled()) log
            .error("Response for this request has been rendered already ");
        throw new IllegalStateException(Util
            .getExceptionMessageString("com.sun.faces.ILLEGAL_ATTEMPT_SETTING_VIEWHANDLER"));
      }
      viewHandler = handler;
      if (log.isDebugEnabled()) log.debug("set ViewHandler Instance to "
          + viewHandler);
    }
  }
So from this method, the viewHandler set later will override the viewHandlers set previously. On the other hand, the variable config in the method configure(ApplicationBean config) is an instance of the class com.sun.faces.config.beans.ApplicationBean which uses an ArrayList for viewHandlers. So it can hold multiple view handlers. Looking into the method call
Util.createInstance(values[i],javax.faces.application.ViewHandler.class, application.getViewHandler())
We can find some tricky things here. The third argument application.getViewHandler() in the above call is important. It acts like a delegate. It returns the ViewHandler set previously. And then it is used as a delegate in the new ViewHandler. So the new ViewHandler does not completely override the previous one. For this to work, the new ViewHandler class needs to have a constructor like Foo(ViewHandler delegate), where the argument delegate is used to hold the old ViewHandler.

A Sample Customized ViewHandler

The following is a sample customerized ViewHandler that incoorparates the default view handler with ICEfaces Facelet viewhandler. It assumes that the facelet files use "xhtml" as the file extension. It also adds the function to customize the URL. This view handler need to be put in the user-deined file faces-config.xml .
public class MyViewHandler extends ViewHandler
  {
    ViewHandler defaultHandler;
    ViewHandler faceletViewHandler;

    public MyViewHandler(ViewHandler defaultHandler)
    {
      this.defaultHandler = defaultHandler;
      this.faceletViewHandler = new D2DFaceletViewHandler(defaultHandler);
    }

    private ViewHandler getViewHandler(String viewId)
    {
      if (viewId.endsWith(".xhtml"))
      {
        return faceletViewHandler;
      }
      return defaultHandler;
    }

    public UIViewRoot createView(FacesContext context, String viewId)
    {
      ViewHandler viewHandler = getViewHandler(viewId);
      return viewHandler.createView(context, viewId);
    }

    public String getActionURL(FacesContext context, String viewId)
  {    
  String actionURL = defaultHandler.getActionURL(context, viewId);
  String customerPrefix = context.getExternalContext()
  .getInitParameter("USER_DEIFINED_PREFIX_NAME");    
  return (customerPrefix == null) ? actionURL, customerPrefix + actionURL;
  }

    public String getResourceURL(FacesContext context, String path)
  {  
  String resourceUrl = defaultHandler.getResourceURL(context, path);
  return (customerPrefix == null) ? resourceUrl, customerPrefix + resourceUrl;
  }

    public void renderView(FacesContext context, UIViewRoot viewToRender)
        throws IOException, FacesException
    {
      ViewHandler viewHandler = getViewHandler(viewToRender.getViewId());
      viewHandler.renderView(context, viewToRender);
    }

    public UIViewRoot restoreView(FacesContext context, String viewId)
    {
      ViewHandler viewHandler = getViewHandler(viewId);
      return viewHandler.restoreView(context, viewId);
    }
  }

Appendix:

The following is more log text following the xml parsing logs:
2009-06-16 13:44:23,606 DEBUG [ConfigureListener]: parse(file:/C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/upload/fooApp/app/fooApp/fooApp/WEB-INF/faces-config.xml)
2009-06-16 13:44:23,622 DEBUG [FacesConfigBean]: addConverterById(myConverter1)
......
2009-06-16 13:44:23,622 DEBUG [FacesConfigBean]: addConverterById(myConverter2)
2009-06-16 13:44:23,653 DEBUG [FacesConfigBean]: addManagedBean(myBean)
2009-06-16 13:44:23,653 DEBUG [ConfigureListener]: setApplicationFactory(com.sun.faces.application.ApplicationFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setFacesContextFactory(com.sun.faces.context.FacesContextFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setFacesContextFactory(com.icesoft.faces.context.FacesContextFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setLifecycleFactory(com.sun.faces.lifecycle.LifecycleFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setRenderKitFactory(com.sun.faces.renderkit.RenderKitFactoryImpl)
2009-06-16 13:44:23,778 DEBUG [ApplicationFactoryImpl]: Created ApplicationFactory 
2009-06-16 13:44:23,809 DEBUG [ApplicationImpl]: Created Application instance 
2009-06-16 13:44:23,809 DEBUG [ApplicationFactoryImpl]: Created Application instance com.sun.faces.application.ApplicationImpl@4f0638
2009-06-16 13:44:23,841 DEBUG [LifecycleFactoryImpl]: Created Default Lifecycle
2009-06-16 13:44:23,841 DEBUG [LifecycleFactoryImpl]: getLifecycle: DEFAULT com.sun.faces.lifecycle.LifecycleImpl@1944f2c
2009-06-16 13:44:23,841 DEBUG [ConfigureListener]: addPhaseListener(org.springframework.webflow.executor.jsf.FlowPhaseListener)
2009-06-16 13:44:23,887 DEBUG [LifecycleImpl]: addPhaseListener(ANY 0,org.springframework.webflow.executor.jsf.FlowPhaseListener@11bfb7e
2009-06-16 13:44:23,887 DEBUG [ConfigureListener]: addPhaseListener(myCustomPhaseListener)
2009-06-16 13:44:23,887 DEBUG [LifecycleImpl]: addPhaseListener(INVOKE_APPLICATION 5,myCustomPhaseListener@afad79
2009-06-16 13:44:23,887 DEBUG [ConfigureListener]: setActionListener(com.sun.faces.application.ActionListenerImpl)
2009-06-16 13:44:23,887 DEBUG [ApplicationImpl]: set ActionListener Instance to com.sun.faces.application.ActionListenerImpl@ad342a
2009-06-16 13:44:23,903 DEBUG [ConfigureListener]: setNavigationHandler(com.sun.faces.application.NavigationHandlerImpl)
2009-06-16 13:44:23,903 DEBUG [NavigationHandlerImpl]: Created NavigationHandler instance 
2009-06-16 13:44:23,903 DEBUG [NavigationHandlerImpl]: Created NavigationHandler instance 
2009-06-16 13:44:23,903 DEBUG [ApplicationImpl]: set NavigationHandler Instance to com.sun.faces.application.NavigationHandlerImpl@16c0caa
2009-06-16 13:44:23,903 DEBUG [ConfigureListener]: setNavigationHandler(org.springframework.webflow.executor.jsf.FlowNavigationHandler)
2009-06-16 13:44:24,012 DEBUG [ApplicationImpl]: set NavigationHandler Instance to org.springframework.webflow.executor.jsf.FlowNavigationHandler@305d34
2009-06-16 13:44:24,012 DEBUG [ConfigureListener]: setPropertyResolver(com.sun.faces.el.PropertyResolverImpl)
2009-06-16 13:44:24,012 DEBUG [ApplicationImpl]: set PropertyResolver Instance to com.sun.faces.el.PropertyResolverImpl@18850b0
2009-06-16 13:44:24,012 DEBUG [ConfigureListener]: setStateManager(com.sun.faces.application.StateManagerImpl)
2009-06-16 13:44:24,028 DEBUG [ApplicationImpl]: set StateManager Instance to com.sun.faces.application.StateManagerImpl@5aa65e
2009-06-16 13:44:24,028 DEBUG [ConfigureListener]: setVariableResolver(com.sun.faces.el.VariableResolverImpl)
2009-06-16 13:44:24,028 DEBUG [ApplicationImpl]: set VariableResolver Instance to com.sun.faces.el.VariableResolverImpl@89b9bf
2009-06-16 13:44:24,028 DEBUG [ConfigureListener]: setVariableResolver(org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver)
2009-06-16 13:44:24,028 DEBUG [ApplicationImpl]: set VariableResolver Instance to org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver@febedf
2009-06-16 13:44:24,028 DEBUG [ConfigureListener]: setViewHandler(com.sun.faces.application.ViewHandlerImpl)
2009-06-16 13:44:24,044 DEBUG [ViewHandlerImpl]: Created ViewHandler instance 
2009-06-16 13:44:24,044 DEBUG [ApplicationImpl]: set ViewHandler Instance to com.sun.faces.application.ViewHandlerImpl@6e1156
2009-06-16 13:44:24,044 DEBUG [ConfigureListener]: setViewHandler(com.icesoft.faces.application.D2DViewHandler)
2009-06-16 13:44:24,075 INFO  [D2DViewHandler]: 
ICEsoft Technologies, Inc.
ICEfaces 1.6.2 
Build number: 22
Revision: 15177

No comments:

Post a Comment