Package org.apache.shale.tiger.view.faces

Source Code of org.apache.shale.tiger.view.faces.LifecycleListener2

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shale.tiger.view.faces;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseListener;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shale.tiger.config.FacesConfigConfig;
import org.apache.shale.tiger.config.FacesConfigParser;
import org.apache.shale.tiger.managed.Bean;
import org.apache.shale.tiger.managed.Property;
import org.apache.shale.tiger.managed.Value;
import org.apache.shale.tiger.managed.config.ManagedBeanConfig;
import org.apache.shale.tiger.managed.config.ManagedPropertyConfig;
import org.apache.shale.tiger.register.FacesComponent;
import org.apache.shale.tiger.register.FacesConverter;
import org.apache.shale.tiger.register.FacesPhaseListener;
import org.apache.shale.tiger.register.FacesRenderer;
import org.apache.shale.tiger.register.FacesValidator;
import org.apache.shale.tiger.register.faces.PhaseListenerAdapter;
import org.apache.shale.tiger.view.Activate;
import org.apache.shale.tiger.view.Destroy;
import org.apache.shale.tiger.view.Init;
import org.apache.shale.tiger.view.Passivate;
import org.apache.shale.tiger.view.Preprocess;
import org.apache.shale.tiger.view.Prerender;
import org.apache.shale.tiger.view.Request;
import org.apache.shale.tiger.view.Session;
import org.apache.shale.tiger.view.View;
import org.apache.shale.util.Messages;
import org.apache.shale.view.AbstractApplicationBean;
import org.apache.shale.view.AbstractRequestBean;
import org.apache.shale.view.AbstractSessionBean;
import org.apache.shale.view.ViewController;
import org.apache.shale.view.faces.FacesConstants;
import org.apache.shale.view.faces.LifecycleListener;
import org.xml.sax.SAXException;

/**
* <p>Specialized version of
* <code>org.apache.shale.view.faces.LifecycleListener</code>
* that implements callbacks to methods tagged by appropriate annotations,
* rather than requiring the containing classes to implement a particular
* interface or extend a particular subclass.</p>
*
* <p><strong>IMPLEMENTATION NOTE:</strong> The standard LifecycleListener
* instance will <em>delegate</em> to methods of this class after
* performing its own appropriate processing.  Therefore, implementation
* methods must <strong>NOT</strong> call their superclass counterparts.
* Doing so will cause any infinite recursion and ultimately a stack
* overflow error.</p>
*
* $Id: LifecycleListener2.java 489966 2006-12-24 01:43:42Z craigmcc $
*
* @since 1.0.3
*/
public class LifecycleListener2 extends LifecycleListener {


    // ------------------------------------------------------------- Constructor


    /**
     * <p>Create a new lifecycle listener.</p>
     */
    public LifecycleListener2() {
    }


    // ------------------------------------------------------ Manifest Constants


    /**
     * <p> Servlet context init parameter which defines which packages to scan
     * for beans.</p>
     */
    public static final String SCAN_PACKAGES =
            "org.apache.shale.tiger.SCAN_PACKAGES";


    /**
     * <p>Application scope attribute under which a configured
     * {@link FacesConfigConfig} bean will be stored, containing
     * information parsed from the relevant <code>faces-config.xml</code>
     * resource(s) for this application.</p>
     */
    public static final String FACES_CONFIG_CONFIG =
            "org.apache.shale.tiger.FACES_CONFIG_CONFIG";


    /**
     * <p>Context relative path to the default <code>faces-config.xml</code>
     * resource for a JavaServer Faces application.</p>
     */
    private static final String FACES_CONFIG_DEFAULT =
            "/WEB-INF/faces-config.xml";


    /**
     * <p>Resource path used to acquire implicit resources buried
     * inside application JARs.</p>
     */
    private static final String FACES_CONFIG_IMPLICIT =
            "META-INF/faces-config.xml";


    /**
     * <p>Prefix path used to locate web application classes for this
     * web application.</p>
     */
    private static final String WEB_CLASSES_PREFIX = "/WEB-INF/classes/";


    /**
     * <p>Prefix path used to locate web application libraries for this
     * web application.</p>
     */
    private static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";


    // ------------------------------------------------------ Instance Variables


    /**
     * <p>Parameter values array that passes no parameters.</p>
     */
    private static final Object[] PARAMETERS = new Object[0];


    /**
     * <p>The <code>ServletContext</code> instance for this application.</p>
     */
    private transient ServletContext servletContext = null;


    // ------------------------------------------ ServletContextListener Methods


    /**
     * <p>Respond to a context initialized event.  Forcibly replace
     * the managed bean services that are different when the Tiger
     * extensions are loaded.  Then, process the <code>faces-config.xml</code>
     * resources for this application in order to record the configuration
     * of managed beans.</p>
     *
     * @param event Event to be processed
     */
    public void contextInitialized(ServletContextEvent event) {

        if (log().isInfoEnabled()) {
            log().info(messages().getMessage("lifecycle.initialized"));
        }

        // Replace the ViewControllerCallbacks handler
        servletContext = event.getServletContext();
        servletContext.setAttribute(FacesConstants.VIEW_CALLBACKS,
                                    new ViewControllerCallbacks2());

        // Create an empty FacesConfigConfig instance and stash it as
        // an application scope attribute
        FacesConfigConfig config = facesConfigConfig();
        servletContext.setAttribute(FACES_CONFIG_CONFIG, config);

        // Determine if the JSF implementation has been initialized yet.
        // If it has, we will be able to register interesting classes
        // directly.  Otherwise, they will have to be queued up for
        // later processing.
        Application application = null;
        try {
            application = application();
        } catch (Exception e) {
            ; // Null means it is not initialized yet
        }

        List<Class> classes;

        String scanPackages = servletContext.getInitParameter(SCAN_PACKAGES);
        if (scanPackages != null) {
            // Scan the classes configured by the scan_packages context parameter
            try {
                classes = packageClasses(servletContext, scanPackages);
            } catch (ClassNotFoundException e) {
                throw new FacesException(e);
            } catch (IOException e) {
                throw new FacesException(e);
            }
        }
        else {
            // Scan the classes in /WEB-INF/classes for interesting annotations
            try {
                classes = webClasses(servletContext);
            } catch (ClassNotFoundException e) {
                throw new FacesException(e);
            }
        }

        try {
            for (Class clazz : classes) {
                if (application != null) {
                    registerClass(clazz, application);
                } else {
                    queueClass(clazz);
                }
                scanClass(clazz, config);
            }
        } catch (Exception e) {
            throw new FacesException(e);
        }

        if (scanPackages == null) {
            // Scan the classes in /WEB-INF/lib for interesting annotations
            List<JarFile> archives = null;
            try {
                archives = webArchives(servletContext);
                for (JarFile archive : archives) {
                    classes = archiveClasses(servletContext, archive);
                    for (Class clazz : classes) {
                        if (application != null) {
                            registerClass(clazz, application);
                        } else {
                            queueClass(clazz);
                        }
                        scanClass(clazz, config);
                    }
                }
            } catch (Exception e) {
                throw new FacesException(e);
            }
        }

        // Create a parser instance used to parse faces-config.xml resources
        FacesConfigParser parser = facesConfigParser(config);
        List<URL> resources = null;
        URL url = null;

        // Parse the implicit faces-config.xml resources for this application
        try {
            resources = implicitResources(servletContext);
            for (URL resource : resources) {
                url = resource;
                parseResource(parser, resource);
            }
        } catch (Exception e) {
            throw new FacesException(messages().getMessage("lifecycle.exception",
                                                           new Object[] { url.toExternalForm() }), e);
        }

        // Parse the configured faces-config.xml resource(s) for this application
        try {
            resources = explicitResources(servletContext);
            for (URL resource : resources) {
                url = resource;
                parseResource(parser, resource);
            }
        } catch (Exception e) {
            throw new FacesException(messages().getMessage("lifecycle.exception",
                                                           new Object[] { url.toExternalForm() }), e);
        }

        // If not already processed, and if it actually exists, parse
        // the default faces-config.xml resource for this application
        try {
            url = servletContext.getResource(FACES_CONFIG_DEFAULT);
            if ((url != null) && !resources.contains(url)) {
                parseResource(parser, url);
            }
        } catch (Exception e) {
            throw new FacesException(messages().getMessage("lifecycle.exception",
                                                           new Object[] { url.toExternalForm() }), e);
        }

        if (log().isInfoEnabled()) {
            log().info(messages().getMessage("lifecycle.completed"));
        }

    }

    /**
     * <p>Return a list of the classes defined within the given packages
     * If there are no such classes, a zero-length list will be returned.</p>
     *
     * @param scanPackages the package configuration
     *
     * @exception ClassNotFoundException if a located class cannot be loaded
     * @exception IOException if an input/output error occurs
     */
    private List<Class> packageClasses(ServletContext servletContext, String scanPackages)
      throws ClassNotFoundException, IOException {

        List<Class> list = new ArrayList<Class>();

        String[] scanPackageTokens =  scanPackages.split(",");
        for (String scanPackageToken : scanPackageTokens)
        {
            if (scanPackageToken.toLowerCase().endsWith(".jar"))
            {
                URL jarResource = servletContext.getResource(WEB_LIB_PREFIX + scanPackageToken);
                String jarURLString = "jar:" + jarResource.toString() + "!/";
                URL url = new URL(jarURLString);
                JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();

                list.addAll(archiveClasses(servletContext, jarFile));
            }
            else
            {
                PackageInfo.getInstance().getClasses(list, scanPackageToken);
            }
        }
        return list;
    }


    /**
     * <p>Respond to a context destroyed event.  Clean up our allocated
     * application scope attributes.</p>
     *
     * @param event Event to be processed
     */
    public void contextDestroyed(ServletContextEvent event) {

        if (log().isInfoEnabled()) {
            log().info(messages().getMessage("lifecycle.destroyed"));
        }

        // Clean up our allocated application scope attributes
        event.getServletContext().removeAttribute(FACES_CONFIG_CONFIG);

    }


    // --------------------------------- ServletContextAttributeListener Methods


    /**
     * <p>Respond to an application scope attribute being added.  If the
     * value is an {@link AbstractApplicationBean}, call its
     * <code>init()</code> method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeAdded(ServletContextAttributeEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireApplicationInit(value);
        }

    }


    /**
     * <p>Respond to an application scope attribute being replaced.
     * If the old value was an {@link AbstractApplicationBean}, call
     * its <code>destroy()</code> method.  If the new value is an
     * {@link AbstractApplicationBean}, call its <code>init()</code>
     * method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeReplaced(ServletContextAttributeEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireApplicationDestroy(value);
        }

        value = event.getServletContext().getAttribute(event.getName());
        if (value != null) {
            fireApplicationInit(value);
        }

    }


    /**
     * <p>Respond to an application scope attribute being removed.
     * If the old value was an {@link AbstractApplicationBean}, call
     * its <code>destroy()</code> method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeRemoved(ServletContextAttributeEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireApplicationDestroy(value);
        }

    }


    // --------------------------------------------- HttpSessionListener Methods


    /**
     * <p>Respond to a session created event.  No special processing
     * is required.</p>
     *
     * @param event Event to be processed
     */
    public void sessionCreated(HttpSessionEvent event) {

        // No special processing is required

    }


    /**
     * <p>Respond to a session destroyed event.  No special
     * processing is required</p>
     *
     * @param event Event to be processed
     */
    public void sessionDestroyed(HttpSessionEvent event) {

        // No special processing is required

    }


    // ----------------------------------- HttpSessionActivationListener Methods


    /**
     * <p>Respond to a "session will passivate" event.  Notify all session
     * scope attributes that are {@link AbstractSessionBean}s.</p>
     *
     * @param event Event to be processed
     */
    public void sessionWillPassivate(HttpSessionEvent event) {

        Enumeration names = event.getSession().getAttributeNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            Object value = event.getSession().getAttribute(name);
            if (value != null) {
                fireSessionPassivate(value);
            }
        }

    }


    /**
     * <p>Respond to a "session did activate" event.  Notify all session
     * scope attributes that are {@link AbstractSessionBean}s.</p>
     *
     * @param event Event to be processed
     */
    public void sessionDidActivate(HttpSessionEvent event) {

        Enumeration names = event.getSession().getAttributeNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            Object value = event.getSession().getAttribute(name);
            if (value != null) {
                fireSessionActivate(value);
            }
        }

    }


    // ------------------------------------ HttpSessionAttributeListener Methods


    /**
     * <p>Respond to a session scope attribute being added.  If the
     * value is an {@link AbstractSessionBean}, call its
     * <code>init()</code> method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeAdded(HttpSessionBindingEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireSessionInit(value);
        }

    }


    /**
     * <p>Respond to a session scope attribute being replaced.
     * If the old value was an {@link AbstractSessionBean}, call
     * its <code>destroy()</code> method.  If the new value is an
     * {@link AbstractSessionBean}, call its <code>init()</code>
     * method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeReplaced(HttpSessionBindingEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireSessionDestroy(value);
        }

        value = event.getSession().getAttribute(event.getName());
        if (value != null) {
            fireSessionInit(value);
        }

    }


    /**
     * <p>Respond to a session scope attribute being removed.
     * If the old value was an {@link AbstractSessionBean}, call
     * its <code>destroy()</code> method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeRemoved(HttpSessionBindingEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireSessionDestroy(value);
        }

    }


    // ------------------------------------------ ServletRequestListener Methods


    /**
     * <p>Respond to a request created event.  If we have accumulated any
     * classes to register with our JSF implementation (but could not initially
     * because it was not initialized before we were), register them now.</p>
     *
     * @param event Event to be processed
     */
    public void requestInitialized(ServletRequestEvent event) {

        queueRegister();

    }


    /**
     * <p>Respond to a request destroyed event.  Cause any instance of
     * ViewController or AbstractRequestBean, plus any bean whose class
     * contains the appropriate annotations, to be removed (which will trigger
     * an attribute removed event).</p>
     *
     * @param event Event to be processed
     */
    public void requestDestroyed(ServletRequestEvent event) {

        // FIXME - may be more performant to just cause all request attributes
        // to be removed, because events will only be fired on relevant ones
        // --------------------------------------------------------------------
        // Remove any AbstractRequestBean or ViewController attributes,
        // as well as beans whose class contains the relevant annotations
        // which will trigger an attributeRemoved event
        List list = new ArrayList();
        ServletRequest request = event.getServletRequest();
        Enumeration names = request.getAttributeNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            Object value = request.getAttribute(name);
            // Direct implementations of the relevant APIs are included
            if ((value instanceof AbstractRequestBean) || (value instanceof ViewController)) {
                list.add(name);
                continue;
            }
            // So are beans whose class implements the relevant annotations
            Class clazz = value.getClass();
            if ((clazz.getAnnotation(Request.class) != null)
             || (clazz.getAnnotation(View.class) != null)) {
                 list.add(name);
                 continue;
             }
        }
        Iterator keys = list.iterator();
        while (keys.hasNext()) {
            String key = (String) keys.next();
            event.getServletRequest().removeAttribute(key);
        }

        // No special processing is required

    }


    // --------------------------------- ServletRequestAttributeListener Methods


    /**
     * <p>Respond to a request scope attribute being added.  If the
     * value is an {@link AbstractRequestBean}, call its <code>init()</code> method.
     * </p>
     *
     * @param event Event to be processed
     */
    public void attributeAdded(ServletRequestAttributeEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireRequestInit(value);
        }

    }


    /**
     * <p>Respond to a request scope attribute being replaced.
     * If the old value was an {@link AbstractRequestBean},
     * call its <code>destroy()</code> method.  If the new value is an
     * {@link AbstractRequestBean}, call its <code>init()</code> method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeReplaced(ServletRequestAttributeEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireRequestDestroy(value);
        }

        value = event.getServletRequest().getAttribute(event.getName());
        if (value != null) {
            fireRequestInit(value);
        }

    }


    /**
     * <p>Respond to a request scope attribute being removed.
     * If the old value was an {@link AbstractRequestBean},
     * call its <code>destroy()</code> method.</p>
     *
     * @param event Event to be processed
     */
    public void attributeRemoved(ServletRequestAttributeEvent event) {

        Object value = event.getValue();
        if (value != null) {
            fireRequestDestroy(value);
        }

    }


    // ------------------------------------------------------- Protected Methods


    /**
     * <p>Fire a destroy event on an @{link AbstractApplicationBean}.</p>
     *
     * @param bean {@link AbstractApplicationBean} to fire event on
     */
    protected void fireApplicationDestroy(Object bean) {

        if (bean instanceof AbstractApplicationBean) {
            super.fireApplicationDestroy(bean);
            return;
        }

        try {
            Method method = method(bean, Destroy.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire an init event on an {@link AbstractApplicationBean}.</p>
     *
     * @param bean {@link AbstractApplicationBean} to fire event on
     */
    protected void fireApplicationInit(Object bean) {

        if (bean instanceof AbstractApplicationBean) {
            super.fireApplicationInit(bean);
            return;
        }

        try {
            Method method = method(bean, Init.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire a destroy event on an @{link AbstractRequestBean}.</p>
     *
     * @param bean {@link AbstractRequestBean} to fire event on
     */
    protected void fireRequestDestroy(Object bean) {

        if ((bean instanceof AbstractRequestBean)
            || (bean instanceof ViewController)) {
            super.fireRequestDestroy(bean);
            return;
        }

        try {
            Method method = method(bean, Destroy.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire an init event on an {@link AbstractRequestBean}.</p>
     *
     * @param bean {@link AbstractRequestBean} to fire event on
     */
    protected void fireRequestInit(Object bean) {

        if ((bean instanceof AbstractRequestBean)
            || (bean instanceof ViewController)) {
            super.fireRequestInit(bean);
            return;
        }

        try {
            Method method = method(bean, Init.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire an activate event on an @{link AbstractSessionBean}.</p>
     *
     * @param bean {@link AbstractSessionBean} to fire event on
     */
    protected void fireSessionActivate(Object bean) {

        if (bean instanceof AbstractSessionBean) {
            super.fireSessionActivate(bean);
            return;
        }

        try {
            Method method = method(bean, Activate.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire a destroy event on an @{link AbstractSessionBean}.</p>
     *
     * @param bean {@link AbstractSessionBean} to fire event on
     */
    protected void fireSessionDestroy(Object bean) {

        if (bean instanceof AbstractSessionBean) {
            super.fireSessionDestroy(bean);
            return;
        }

        try {
            Method method = method(bean, Destroy.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire an init event on an {@link AbstractSessionBean}.</p>
     *
     * @param bean {@link AbstractSessionBean} to fire event on
     */
    protected void fireSessionInit(Object bean) {

        if (bean instanceof AbstractSessionBean) {
            super.fireSessionInit(bean);
            return;
        }

        try {
            Method method = method(bean, Init.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    /**
     * <p>Fire an passivate event on an @{link AbstractSessionBean}.</p>
     *
     * @param bean {@link AbstractSessionBean} to fire event on
     */
    protected void fireSessionPassivate(Object bean) {

        if (bean instanceof AbstractSessionBean) {
            super.fireSessionPassivate(bean);
            return;
        }

        try {
            Method method = method(bean, Passivate.class);
            if (method != null) {
                method.invoke(bean, PARAMETERS);
            }
        } catch (InvocationTargetException e) {
            handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
        } catch (Exception e) {
            handleException(FacesContext.getCurrentInstance(), e);
        }

    }


    // --------------------------------------------------------- Private Methods


    /**
     * <p>The Application instance for this application.</p>
     */
    private Application application = null;


    /**
     * <p>Return the <code>Application</code> for this application.</p>
     */
    private Application application() {

        if (application == null) {
            application = ((ApplicationFactory) FactoryFinder.
              getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication();
        }
        return application;

    }


    /**
     * <p>Return a list of classes to examine from the specified JAR archive.
     * If this archive has no classes in it, a zero-length list is returned.</p>
     *
     * @param context <code>ServletContext</code> instance for
     *  this application
     * @param jar <code>JarFile</code> for the archive to be scanned
     *
     * @exception ClassNotFoundException if a located class cannot be loaded
     */
    private List<Class> archiveClasses(ServletContext context, JarFile jar)
        throws ClassNotFoundException {

        // Accumulate and return a list of classes in this JAR file
        List<Class> list = new ArrayList<Class>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = this.getClass().getClassLoader();
        }
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.isDirectory()) {
                continue;                         // This is a directory
            }
            String name = entry.getName();
            if (name.startsWith("META-INF/")) {
                continue;                         // Attribute files
            }
            if (!name.endsWith(".class")) {
                continue;                         // This is not a class
            }
            name = name.substring(0, name.length() - 6); // Trim ".class"
            Class clazz = null;
            try {
                clazz = loader.loadClass(name.replace('/', '.'));
            } catch (NoClassDefFoundError e) {
                // Skip this class - we cannot analyze classes we cannot load
            } catch (Exception e) {
                // Skip this class - we cannot analyze classes we cannot load
            }
            if (clazz != null) {
                list.add(clazz);
            }
        }
        return list;

    }


    /**
     * <p>Return a list of URLs of <code>faces-config.xml</code>
     * resources for this web application that have been explicitly listed
     * in the appropriate context initialization parameter.  If there are no
     * such resources, a zero-length list is returned.</p>
     *
     * @param servletContext <code>ServletContext</code> instance for this
     *  application
     *
     * @exception MalformedURLException if a context relative resource path
     *  has incorrect syntax
     */
    private List<URL> explicitResources(ServletContext servletContext)
        throws MalformedURLException {

        // Create an empty list to contain our results
        List<URL> list = new ArrayList<URL>();

        // If no initialization parameter was listed, return an empty list
        String resources = servletContext.getInitParameter("javax.faces.CONFIG_FILES");
        if (resources == null) {
            return list;
        }

        // Parse the comma-delimited list of context-relative resource paths
        while (resources.length() > 0) {
            int comma = resources.indexOf(',');
            if (comma < 0) {
                resources = resources.trim();
                if (resources.length() > 0) {
                    URL url = servletContext.getResource(resources);
                    if (url != null) {
                        list.add(url);
                    }
                }
                resources = "";
            } else {
                URL url = servletContext.getResource(resources.substring(0, comma).trim());
                if (url != null) {
                    list.add(url);
                }
                resources = resources.substring(comma + 1);
            }
        }

        // Return the completed list
        return list;

    }

    /**
     * <p>Create and return an empty {@link FacesConfigConfig} bean that will
     * be filled with information later on.</p>
     */
    private FacesConfigConfig facesConfigConfig() {

        return new FacesConfigConfig();

    }


    /**
     * <p>Create and return a configured {@link FacesConfigParser} instance
     * to be used for parsing <code>faces-config.xml</code> resources.  The
     * caller will need to set the <code>resource</code> property on this
     * instance before calling the <code>parse()</code> method.</p>
     *
     * @param config <code>FacesConfigBean</code> used to store the
     *  information gathered while parsing configuration resources
     */
    private FacesConfigParser facesConfigParser(FacesConfigConfig config) {

        FacesConfigParser parser = new FacesConfigParser();
        parser.setFacesConfig(config);
        parser.setValidating(true);
        return parser;

    }


    /**
     * <p>Return an array of all <code>Field</code>s reflecting declared
     * fields in this class, or in any superclass other than
     * <code>java.lang.Object</code>.</p>
     *
     * @param clazz Class to be analyzed
     */
    private Field[] fields(Class clazz) {

        Map<String,Field> fields = new HashMap<String,Field>();
        do {
            for (Field field : clazz.getDeclaredFields()) {
                if (!fields.containsKey(field.getName())) {
                    fields.put(field.getName(), field);
                }
            }
        } while ((clazz = clazz.getSuperclass()) != Object.class);
        return (Field[]) fields.values().toArray(new Field[fields.size()]);

    }



    /**
     * <p>Return a list of URLs to implicit configuration resources
     * embedded in this application.</p>
     *
     * @param servletContext <code>ServletContext</code> instance for this
     *  application
     *
     * @exception IOException if an input/output error occurs
     */
    private List<URL> implicitResources(ServletContext servletContext)
        throws IOException {

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = this.getClass().getClassLoader();
        }
        Enumeration items = loader.getResources(FACES_CONFIG_IMPLICIT);
        List<URL> list = new ArrayList<URL>();
        while (items.hasMoreElements()) {
            list.add((URL) items.nextElement());
        }
        return list;

    }


    /**
     * <p>The lifecycle instance for this application.</p>
     */
    private Lifecycle lifecycle = null;


    /**
     * <p>Return the <code>Lifecycle</code> for this application.</p>
     */
    private Lifecycle lifecycle() {

        if (lifecycle == null) {
            String lifecycleId = servletContext.getInitParameter("javax.faces.LIFECYCLE_ID");
            if (lifecycleId == null) {
                lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
            }
            lifecycle = ((LifecycleFactory) FactoryFinder.
              getFactory(FactoryFinder.LIFECYCLE_FACTORY)).getLifecycle(lifecycleId);
        }
        return lifecycle;

    }


    /**
     * <p>The <code>Log</code> instance we will be using.</p>
     */
    private transient Log log = null;


    /**
     * <p>Return the <code>Log</code> instance to be used for this class,
     * instantiating a new one if necessary.</p>
     */
    private Log log() {

        if (log == null) {
            log = LogFactory.getLog(LifecycleListener2.class);
        }
        return log;

    }


    /**
     * <p>The <code>Messages</code> instance we will be using.</p>
     */
    private transient Messages messages = null;


    /**
     * <p>Return the <code>Messages</code> instance to be used for this class,
     * instantiating a new one if necessary.</p>
     */
    private Messages messages() {

        if (messages == null) {
            messages = new Messages("org.apache.shale.tiger.faces.Bundle",
                                    Thread.currentThread().getContextClassLoader());
        }
        return messages;

    }


    /**
     * <p>The set of method annotations for callbacks of interest.</p>
     */
    private static final Class[] annotations =
    { Init.class, Preprocess.class, Prerender.class, Destroy.class,
      Activate.class, Passivate.class };



    /**
     * <p>The set of class annotations for classes of interest.</p>
     */
    private static final Class[] markers =
    { View.class, Request.class, Session.class,
      org.apache.shale.tiger.view.Application.class };



    /**
     * <p>Data structure to maintain information about annotated
     * methods.  In this map, the key is the Class being analyzed,
     * and the value is an inner map.  In the inner map, the key
     * is an Annotation class, and the value is the corresponding
     * Method instance.</p>
     */
    private transient Map<Class,Map<Class,Method>> maps =
      new HashMap<Class,Map<Class,Method>>();


    /**
     * <p>Return the <code>Method</code> to be called for the specified
     * annotation on the specified instance, if any.  If there is no such
     * method, return <code>null</code>.</p>
     *
     * @param instance Instance on which callbacks will be performed
     * @param annotation Annotation for which to return a method
     */
    private Method method(Object instance, Class annotation) {


        // Does the underlying class implement a relevant class annotation?
        // If not, exit early
        Class clazz = instance.getClass();
        boolean found = false;
        for (Class marker : markers) {
            if (clazz.getAnnotation(marker) != null) {
                found = true;
                break;
            }
        }
        if (!found) {
            return null;
        }

        synchronized (maps) {

            // If we have seen this Class already, simply return the
            // previously located Method (if any)
            Map<Class,Method> map = maps.get(clazz);
            if (map != null) {
                return map.get(annotation);
            }


            // Construct and cache a new Map identifying the
            // methods of interest for these callbacks
            map = new HashMap<Class,Method>();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getParameterTypes().length > 0) {
                    continue;
                }
                for (Class anno : annotations) {
                    if (method.getAnnotation(anno) != null) {
                        map.put(anno, method);
                    }
                }
            }
            maps.put(clazz, map);
            return map.get(annotation);

        }

    }


    /**
     * <p>Use the specified parser to parse the resource at the specified
     * URL, which will accumulate additional information into the
     * <code>FacesConfigConfig</code> instance configured on the parser.</p>
     *
     * @param parser FacesConfigParser instance to be used
     * @param resource URL of the resource to be parsed
     *
     * @exception IOException if an input/output error occurs
     * @exception SAXException if an XML parsing error occurs
     */
    private void parseResource(FacesConfigParser parser, URL resource)
        throws IOException, SAXException {

        if (log().isDebugEnabled()) {
            log().debug("Parsing faces-config.xml resource '"
                        + resource.toExternalForm() + "'");
        }
        parser.setResource(resource);
        parser.parse();

    }



    /**
     * <p>A list of classes that need to be registered with the JSF implementation
     * after it has been started.</p>
     */
    private List<Class> queue = new ArrayList<Class>();


    /**
     * <p>Queue the specified class to be registered (via <code>registerClass()</code>)
     * at a later time.</p>
     *
     * @param clazz Class instance to be queued
     */
    private void queueClass(Class clazz) {
        queue.add(clazz);
    }


    /**
     * <p>Register any classes that have been queued.  This method is synchronzied
     * because it is called from a request listener, and may therefore be subject
     * to race conditions if multiple requests are received simultaneously.</p>
     */
    private synchronized void queueRegister() {

        if (queue == null) {
            return;
        }
        for (Class clazz : queue) {
            registerClass(clazz, application());
        }
        queue = null;

    }


    /**
     * <p>The render kit factory for this application.</p>
     */
    private RenderKitFactory rkFactory = null;


    /**
     * <p>Return the <code>RenderKitFactory</code> for this application.</p>
     */
    private RenderKitFactory renderKitFactory() {

        if (rkFactory == null) {
            rkFactory = (RenderKitFactory)
              FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
        }
        return rkFactory;

    }


    /**
     * <p>Register the specified class with the specified JavaServer Faces
     * <code>Application</code> instance, based on annotations that the
     * class is annotated with.</p>
     *
     * @param clazz Class to be registered
     * @param application <code>Application</code> instance with which to
     *  register this class
     */
    private void registerClass(Class clazz, Application application) {

        if (log().isTraceEnabled()) {
            log().trace("registerClass(" + clazz.getName() + ")");
        }
        FacesComponent comp = (FacesComponent) clazz.getAnnotation(FacesComponent.class);
        if (comp != null) {
            if (log().isTraceEnabled()) {
                log().trace("addComponent(" + comp.value() + "," + clazz.getName() + ")");
            }
            application().addComponent(comp.value(), clazz.getName());
        }

        FacesConverter conv = (FacesConverter) clazz.getAnnotation(FacesConverter.class);
        if (conv != null) {
            if (log().isTraceEnabled()) {
                log().trace("addConverter(" + conv.value() + "," + clazz.getName() + ")");
            }
            application().addConverter(conv.value(), clazz.getName());
        }

        FacesPhaseListener list = (FacesPhaseListener) clazz.getAnnotation(FacesPhaseListener.class);
        if (list != null) {
            try {
                Lifecycle lifecycle = lifecycle();
                Object instance = clazz.newInstance();
                if (instance instanceof PhaseListener) {
                    lifecycle.addPhaseListener((PhaseListener) instance);
                } else {
                    lifecycle.addPhaseListener(new PhaseListenerAdapter(instance));
                }
            } catch (FacesException e) {
                throw e;
            } catch (Exception e) {
                throw new FacesException(e);
            }
        }

        FacesRenderer rend = (FacesRenderer) clazz.getAnnotation(FacesRenderer.class);
        if (rend != null) {
            String renderKitId = rend.renderKitId();
            if (renderKitId == null) {
                renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT;
            }
            if (log().isTraceEnabled()) {
                log().trace("addRenderer(" + renderKitId + ", " + rend.componentFamily()
                  + ", " + rend.rendererType() + ", " + clazz.getName() + ")");
            }
            try {
                RenderKit rk = renderKitFactory().getRenderKit(null, renderKitId);
                rk.addRenderer(rend.componentFamily(), rend.rendererType(),
                               (Renderer) clazz.newInstance());
            } catch (Exception e) {
                throw new FacesException(e);
            }
        }

        FacesValidator val = (FacesValidator) clazz.getAnnotation(FacesValidator.class);
        if (val != null) {
            if (log().isTraceEnabled()) {
                log().trace("addValidator(" + val.value() + "," + clazz.getName() + ")");
            }
            application().addValidator(val.value(), clazz.getName());
        }

    }


    /**
     * <p>Scan the specified class for those that have annotations
     * of interest, and construct appropriate configuration metadata attached
     * to the specified {@link FacesConfigConfig} bean.</p>
     *
     * @param clazz Class to be scanned
     * @param config {@link FacesConfigConfig} to be updated
     */
    private void scanClass(Class clazz, FacesConfigConfig config) {

        if (log().isTraceEnabled()) {
            log().trace("Scanning class '" + clazz.getName() + "'");
        }
        Bean bean = (Bean) clazz.getAnnotation(Bean.class);
        if (bean != null) {
            if (log().isDebugEnabled()) {
                log().debug("Class '" + clazz.getName() + "' has an @Bean annotation");
            }
            ManagedBeanConfig mbc = new ManagedBeanConfig();
            mbc.setName(bean.name());
            mbc.setType(clazz.getName());
            switch (bean.scope()) {
                case APPLICATION:
                    mbc.setScope("application");
                    break;
                case REQUEST:
                    mbc.setScope("request");
                    break;
                case SESSION:
                    mbc.setScope("session");
                    break;
                default:
                    break;
            }
            Field[] fields = fields(clazz);
            for (Field field : fields) {
                if (log().isTraceEnabled()) {
                    log().trace("  Scanning field '" + field.getName() + "'");
                }
                Property property = (Property) field.getAnnotation(Property.class);
                if (property != null) {
                    if (log().isDebugEnabled()) {
                        log().debug("  Field '" + field.getName() + "' has a @Property annotation");
                    }
                    ManagedPropertyConfig mpc = new ManagedPropertyConfig();
                    String name = property.name();
                    if ((name == null) || "".equals(name)) {
                        name = field.getName();
                    }
                    mpc.setName(name);
                    mpc.setType(field.getType().getName()); // FIXME - primitives, arrays, etc.
                    mpc.setValue(property.value());
                    mbc.addProperty(mpc);
                    continue;
                }
                // Support deprecated @Value annotation as well
                Value value = (Value) field.getAnnotation(Value.class);
                if (value != null) {
                    if (log().isDebugEnabled()) {
                        log().debug("  Field '" + field.getName() + "' has a @Value annotation");
                    }
                    ManagedPropertyConfig mpc = new ManagedPropertyConfig();
                    mpc.setName(field.getName());
                    mpc.setType(field.getType().getName()); // FIXME - primitives, arrays, etc.
                    mpc.setValue(value.value());
                    mbc.addProperty(mpc);
                    continue;
                }
            }
            config.addManagedBean(mbc);
        }

    }


    /**
     * <p>Return a list of the JAR archives defined under the
     * <code>/WEB-INF/lib</code> directory of this web application
     * that contain a <code>META-INF/faces-config.xml</code> resource
     * (even if that resource is empty).  If there are no such JAR archives,
     * a zero-length list will be returned.</p>
     *
     * @param servletContext <code>ServletContext</code> instance for
     *  this application
     *
     * @exception IOException if an input/output error occurs
     */
    private List<JarFile> webArchives(ServletContext servletContext)
        throws IOException {

        List<JarFile> list = new ArrayList<JarFile>();
        Set<Object> paths = servletContext.getResourcePaths(WEB_LIB_PREFIX);
        for (Object pathObject : paths) {
            String path = (String) pathObject;
            if (!path.endsWith(".jar")) {
                continue;
            }
            URL url = servletContext.getResource(path);
            String jarURLString = "jar:" + url.toString() + "!/";
            url = new URL(jarURLString);
            JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
            // Skip this JAR file if it does not have a META-INF/faces-config.xml
            // resource (even if that resource is empty)
            JarEntry signal = jarFile.getJarEntry(FACES_CONFIG_IMPLICIT);
            if (signal == null) {
                if (log().isTraceEnabled()) {
                    log().trace("Skip JAR file " + path + " because it has no META-INF/faces-config.xml resource");
                }
                continue;
            }
            list.add(jarFile);
        }
        return list;

    }


    /**
     * <p>Return a list of the classes defined under the
     * <code>/WEB-INF/classes</code> directory of this web
     * application.  If there are no such classes, a zero-length list
     * will be returned.</p>
     *
     * @param servletContext <code>ServletContext</code> instance for
     *  this application
     *
     * @exception ClassNotFoundException if a located class cannot be loaded
     */
    private List<Class> webClasses(ServletContext servletContext)
        throws ClassNotFoundException {

        List<Class> list = new ArrayList<Class>();
        webClasses(servletContext, WEB_CLASSES_PREFIX, list);
        return list;

    }


    /**
     * <p>Add classes found in the specified directory to the specified
     * list, recursively calling this method when a directory is encountered.</p>
     *
     * @param servletContext <code>ServletContext</code> instance for
     *  this application
     * @param prefix Prefix specifying the "directory path" to be searched
     * @param list List to be appended to
     *
     * @exception ClassNotFoundException if a located class cannot be loaded
     */
    private void webClasses(ServletContext servletContext, String prefix, List<Class> list)
        throws ClassNotFoundException {

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = this.getClass().getClassLoader();
        }
        Set<Object> paths = servletContext.getResourcePaths(prefix);
        if (log().isTraceEnabled()) {
            log().trace("webClasses(" + prefix + ") - Received " + paths.size() + " paths to check");
        }
        String path = null;
        for (Object pathObject : paths) {
            path = (String) pathObject;
            if (path.endsWith("/")) {
                webClasses(servletContext, path, list);
            } else if (path.endsWith(".class")) {
                path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip prefix
                path = path.substring(0, path.length() - 6);        // Strip suffix
                path = path.replace('/', '.'); // Convert to FQCN
                Class clazz = null;
                try {
                    clazz = loader.loadClass(path);
                } catch (NoClassDefFoundError e) {
                    ; // Skip this class - we cannot analyze classes we cannot load
                } catch (Exception e) {
                    ; // Skip this class - we cannot analyze classes we cannot load
                }
                if (clazz != null) {
                    list.add(clazz);
                }
            }
        }

    }



}
TOP

Related Classes of org.apache.shale.tiger.view.faces.LifecycleListener2

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.