com.icesoft.faces.webapp.xmlhttp.PersistentFacesState.java Source code

Java tutorial

Introduction

Here is the source code for com.icesoft.faces.webapp.xmlhttp.PersistentFacesState.java

Source

/*
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * "The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * The Original Code is ICEfaces 1.5 open source software code, released
 * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
 * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
 * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 *
 * Contributor(s): _____________________.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
 * License), in which case the provisions of the LGPL License are
 * applicable instead of those above. If you wish to allow use of your
 * version of this file only under the terms of the LGPL License and not to
 * allow others to use your version of this file under the MPL, indicate
 * your decision by deleting the provisions above and replace them with
 * the notice and other provisions required by the LGPL License. If you do
 * not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the LGPL License."
 *
 */

package com.icesoft.faces.webapp.xmlhttp;

import com.icesoft.faces.context.BridgeExternalContext;
import com.icesoft.faces.context.BridgeFacesContext;
import com.icesoft.faces.context.View;
import com.icesoft.faces.context.ViewListener;
import com.icesoft.faces.webapp.http.common.Configuration;
import com.icesoft.faces.webapp.http.core.SessionExpiredException;
import com.icesoft.faces.webapp.parser.ImplementationUtil;
import com.icesoft.util.SeamUtilities;
import com.icesoft.faces.util.CoreUtils;
import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.Executors;
import edu.emory.mathcs.backport.java.util.concurrent.ThreadFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.portlet.PortletSession;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;

/**
 * The {@link PersistentFacesState} class allows an application to initiate
 * rendering asynchronously and independently of user interaction.
 * <p/>
 * Typical use is to obtain a {@link PersistentFacesState} instance in a managed
 * bean constructor and then use that instance for any relevant rendering
 * requests.
 * <p/>
 * Applications should obtain the <code>PersistentFacesState</code> instance
 * using the public static getInstance() method.  The recommended approach is to
 * call this method from a mangaged-bean constructor and use the instance
 * obtained for any {@link #render} requests.
 * <p/>
 * Application writers that make calls to the <code>execute</code> and
 * <code>render</code> methods should be aware of the potential for deadlock
 * conditions if this code is coupled with a third party framework
 * (eg. Spring Framework) that does its own resource locking.
 */
public class PersistentFacesState implements Serializable {
    private static final Log log = LogFactory.getLog(PersistentFacesState.class);
    private static final ExecutorService executorService = Executors
            .newSingleThreadExecutor(new DaemonThreadFactory());
    private static final InheritableThreadLocal localInstance = new InheritableThreadLocal();

    private final ClassLoader renderableClassLoader;
    private final Lifecycle lifecycle;
    private final boolean synchronousMode;
    private final Collection viewListeners;
    private View view;
    private boolean disposed;
    // For server push, the JSF state saving key generated after each render must
    // be preserved for the next artificial lifecycle so we can restore state
    private String stateRestorationId;

    public PersistentFacesState(View view, Collection viewListeners, Configuration configuration) {
        //JIRA case ICE-1365
        //Save a reference to the web app classloader so that server-side
        //render requests work regardless of how they are originated.
        this.renderableClassLoader = Thread.currentThread().getContextClassLoader();
        LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        this.lifecycle = factory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
        this.view = view;
        this.viewListeners = viewListeners;
        this.synchronousMode = configuration.getAttributeAsBoolean("synchronousUpdate", false);
        this.setCurrentInstance();
    }

    public void dispose() {
        disposed = true;
    }

    public void setCurrentInstance() {
        localInstance.set(this);
    }

    public static boolean isThreadLocalNull() {
        return localInstance.get() == null;
    }

    /**
     * Obtain the <code>PersistentFacesState</code> instance appropriate for the
     * current context.  This is managed through InheritableThreadLocal
     * variables.  The recommended approach is to call this method from a
     * mangaged-bean constructor and use the instance obtained for any {@link
     * #render} requests.
     *
     * @return the PersistentFacesState appropriate for the calling Thread
     */
    public static PersistentFacesState getInstance() {
        return (PersistentFacesState) localInstance.get();
    }

    /**
     * Obtain the {@link PersistentFacesState} instance keyed by viewNumber from
     * the specified sessionMap. This API is not intended for application use.
     *
     * @param sessionMap session-scope parameters
     * @return the PersistentFacesState
     * @deprecated
     */
    public static PersistentFacesState getInstance(Map sessionMap) {
        return getInstance();
    }

    /**
     * Return the FacesContext associated with this instance.
     *
     * @return the FacesContext for this instance
     */
    public FacesContext getFacesContext() {
        return view.getFacesContext();
    }

    /**
     * Returns whether the current view is in synchronous mode, which means
     * that server push is not available.
     *
     * @return If in synchronous mode
     */
    public boolean isSynchronousMode() {
        return synchronousMode;
    }

    /**
     * Render the view associated with this <code>PersistentFacesState</code>.
     * The user's browser will be immediately updated with any changes.
     */
    public void render() throws RenderingException {
        failIfDisposed();
        warnIfSynchronous();
        BridgeFacesContext facesContext = view.getFacesContext();
        try {
            view.acquireLifecycleLock();
            view.installThreadLocals();
            //todo: why is this necessary? is this the best place to have this call?
            facesContext.setFocusId("");
            lifecycle.render(facesContext);
        } catch (Exception e) {
            String viewID = "Unknown View";
            try {
                viewID = facesContext.getViewRoot().getViewId();
            } catch (NullPointerException npe) {
            }
            log.error("Exception occured during execute push on " + viewID, e);
            throwRenderingException(e);
        } finally {
            facesContext.release();
            release();
            view.releaseLifecycleLock();
        }
    }

    /**
     * Render the view associated with this <code>PersistentFacesState</code>.
     * This takes place on a separate thread to guard against potential deadlock
     * from calling {@link #render} during view rendering.
     */
    public void renderLater() {
        warnIfSynchronous();
        executorService.execute(new RenderRunner());
    }

    public void renderLater(long miliseconds) {
        warnIfSynchronous();
        executorService.execute(new RenderRunner(miliseconds));
    }

    /**
     * @param setup    Runnable to run, in the proper thread context, before
     *                 doing the JSF lifecycle
     * @param warnSync Whether warn if in synchronous mode
     */
    public void renderLater(Runnable setup, boolean warnSync) {
        if (warnSync) {
            warnIfSynchronous();
        }
        executorService.execute(new RenderRunner(setup));
    }

    /**
     * Execute  the view associated with this <code>PersistentFacesState</code>.
     * This is typically followed immediatly by a call to
     * {@link PersistentFacesState#render}.
     * <p/>
     * This method obtains and releases the monitor on the FacesContext object.
     * If starting a JSF lifecycle causes 3rd party frameworks to perform locking
     * of their resources, releasing this monitor between the call to this method
     * and the call to {@link PersistentFacesState#render} can allow deadlocks
     * to occur. Use {@link PersistentFacesState#executeAndRender} instead
     *
     * @deprecated this method should not be exposed
     */
    public void execute() throws RenderingException {
        failIfDisposed();
        BridgeFacesContext facesContext = null;
        try {
            view.acquireLifecycleLock();
            view.installThreadLocals();
            facesContext = view.getFacesContext();

            // For JSF 1.1, with the inputFile, we need the execute phases to 
            // actually happen, which wasn't the case when the following code 
            // only ran for JSF 1.2. These are the options we have for JSF 1.1:
            // A. facesContext.renderResponse() skips the phases, so the 
            //    FileUploadPhaseListener can't work. We used to do this.
            // B. Doing nothing means that the old values that 
            //    FileUploadPhaseListener puts in the RequestParameterMap stick 
            //    around for subsequent file upload lifecycles (no problem) and 
            //    server pushes (might cause duplicate inputFile.actionListener 
            //    calls). As well as the values from the last user interaction, 
            //    which might cause problems too.
            // C. Clearing the RequestParameterMap leads to skipping the execute,
            //    so that's not sufficient.
            // D. Just doing the same thing for JSF 1.1 as JSF 1.2 seems to work

            if (true) { // if (ImplementationUtil.isJSF12()) {
                //facesContext.renderResponse() skips phase listeners
                //in JSF 1.2, so do a full execute with no stale input
                //instead
                Map requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
                requestParameterMap.clear();
                if (SeamUtilities.isSeamEnvironment()) {
                    //ICE-2990/JBSEAM-3426 must have empty requestAttributes for push to work with Seam
                    ((BridgeExternalContext) facesContext.getExternalContext()).removeSeamAttributes();
                }
                //Seam appears to need ViewState set during push
                // see below
                //                requestParameterMap.put("javax.faces.ViewState", "ajaxpush");

                // If state saving is turned on, we need to insert the saved state
                // restoration key for this user into the request map for JSF.
                // Even if no state saving, inserting this key will at least
                // cause the JSF lifecycle to run, which we want to do for
                // consistency.
                String postback;
                if (ImplementationUtil.isJSFStateSaving() && stateRestorationId != null) {
                    postback = stateRestorationId;
                } else {
                    postback = "not reload";
                }

                facesContext.getExternalContext().getRequestParameterMap().put(BridgeExternalContext.PostBackKey,
                        postback);

            }
            lifecycle.execute(facesContext);

        } catch (Exception e) {
            release();
            view.releaseLifecycleLock();
            String viewID = "Unknown View";
            try {
                viewID = facesContext.getViewRoot().getViewId();
            } catch (NullPointerException npe) {
            }
            log.error("Exception occured during execute push on " + viewID, e);
            throwRenderingException(e);
        }
    }

    /**
     * Execute the JSF lifecycle (essentially calling <code> execute()</code> and
     * <code>render()</code> ) without releasing the FacesContext monitor
     * at any point between.
     *
     * @throws RenderingException if there is an exception rendering the View
     * @since 1.7
     */
    public void executeAndRender() throws RenderingException {
        view.acquireLifecycleLock();
        view.getFacesContext().injectBundles();
        CoreUtils.addAuxiliaryContexts(view.getFacesContext());
        execute();
        render();
    }

    public void setupAndExecuteAndRender() throws RenderingException {
        view.acquireLifecycleLock();
        installContextClassLoader();
        if (SeamUtilities.isSeamEnvironment()) {
            testSession();
        }
        // JIRA #1377 Call execute before render.
        // #2459 use fully synchronized version internally.
        executeAndRender();
    }

    /**
     * Redirect browser to a different URI. The user's browser will be
     * immediately redirected without any user interaction required. This appears
     * to require the page extension in the defining technology (.jxpx, .xhtml, etc)
     * rather than in the suffix replacement way ( .iface, .faces, etc ) 
     *
     * @param uri the relative or absolute URI.
     */
    public void redirectTo(final String uri) {
        warnIfSynchronous();
        executorService.execute(new Runnable() {
            public void run() {
                try {
                    view.acquireLifecycleLock();
                    view.installThreadLocals();
                    view.getFacesContext().getExternalContext().redirect(uri);
                } catch (Exception e) {
                    log.error("Exception during redirectTo ", e);
                    throw new RuntimeException(e);
                } finally {
                    release();
                    view.releaseLifecycleLock();
                }
            }
        });
    }

    /**
     * Navigate browser to a different page using an outcome argument to
     * select the navigation rule. The mechanism for navigation depends on
     * the navigation rule. <p>
     *
     * If there is no JSF lifecycle in progress when this method is called,
     * this method will manage one. Otherwise, it is the callers responsibility
     * to call the <code>execute</code> method before, and <code>render</code> method 
     * after this navigation method.
     *
     * @param outcome the 'from-outcome' field in the navigation rule.
     */
    public void navigateTo(String outcome) {
        warnIfSynchronous();
        try {
            BridgeFacesContext facesContext = view.getFacesContext();
            FacesContext fc = FacesContext.getCurrentInstance();
            // #4251 start a lifecycle to re-establish the ViewRoot before
            // trying navigation if a lifecycle is not already running.
            boolean manageLifecycle = ((fc == null) || (fc.getViewRoot() == null));
            if (manageLifecycle) {
                execute();
            }
            facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext,
                    facesContext.getViewRoot().getViewId(), outcome);
            if (manageLifecycle) {
                render();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Threads that used to server request/response cycles in an application
     * server are generally pulled from a pool.  Just before the thread is done
     * completing the cycle, we should clear any local instance variables to
     * ensure that they are not hanging on to any session references, otherwise
     * the session and their resources are not released.
     */
    public void release() {
        localInstance.set(null);
    }

    public void installContextClassLoader() {
        try {
            Thread.currentThread().setContextClassLoader(renderableClassLoader);
        } catch (SecurityException se) {
            log.debug("setting context class loader is not permitted", se);
        }
    }

    /**
     * @deprecated use {@link com.icesoft.faces.context.DisposableBean} interface instead
     */
    public void addViewListener(ViewListener listener) {
        if (!viewListeners.contains(listener)) {
            viewListeners.add(listener);
        }
    }

    private class RenderRunner implements Runnable {
        private final long delay;
        private Runnable setup;

        public RenderRunner() {
            delay = 0;
        }

        public RenderRunner(long miliseconds) {
            delay = miliseconds;
        }

        /**
         * @param setup Runnable to run, in the proper thread context, before
         *              doing the JSF lifecycle
         */
        public RenderRunner(Runnable setup) {
            delay = 0;
            this.setup = setup;
        }

        /**
         * <p>Not for application use. Entry point for {@link
         * PersistentFacesState#renderLater}.</p>
         */
        public void run() {
            try {
                Thread.sleep(delay);
                if (setup != null) {
                    setup.run();
                }
                setupAndExecuteAndRender();
            } catch (RenderingException e) {
                log.debug("renderLater failed ", e);
            } catch (InterruptedException e) {
                //ignore
            } catch (IllegalStateException e) {
                log.debug("renderLater failed ", e);
            }
        }
    }

    private static class DaemonThreadFactory implements ThreadFactory {
        private ThreadFactory defaultThreadFactory;

        private DaemonThreadFactory() {
            defaultThreadFactory = Executors.defaultThreadFactory();
        }

        public Thread newThread(Runnable runnable) {
            Thread thread = defaultThreadFactory.newThread(runnable);
            thread.setDaemon(true);
            return thread;
        }
    }

    /**
     * This is not a public API, but is intended for temporary use
     * by UploaderServer only.
     *
     * @deprecated
     */
    public void acquireUploadLifecycleLock() {
        view.acquireLifecycleLock();
    }

    /**
     * This is not a public API, but is intended for temporary use
     * by UploaderServer only.
     *
     * @deprecated
     */
    public void releaseUploadLifecycleLock() {
        view.releaseLifecycleLock();
    }

    /**
     * This is not a public API, but is intended for temporary use
     * by UploaderServer only.
     *
     * @deprecated
     */
    public void setAllCurrentInstances() {
        view.installThreadLocals();
    }

    private void warnIfSynchronous() {
        if (synchronousMode) {
            log.warn("Running in 'synchronous mode'. The page updates were queued but not sent.");
        }
    }

    private void testSession() throws IllegalStateException {
        Object o = view.getFacesContext().getExternalContext().getSession(false);
        if (o != null) {
            if (o instanceof HttpSession) {
                HttpSession session = (HttpSession) o;
                session.getAttributeNames();
            } else if (o instanceof PortletSession) {
                PortletSession ps = (PortletSession) o;
                ps.getAttributeNames();
            }
        }
    }

    private void fatalRenderingException() throws FatalRenderingException {
        final String message = "fatal render failure for " + view;
        log.debug(message);
        throw new FatalRenderingException(message);
    }

    private void fatalRenderingException(Exception e) throws FatalRenderingException {
        final String message = "fatal render failure for " + view;
        log.debug(message, e);
        throw new FatalRenderingException(message, e);
    }

    private void transientRenderingException(Exception e) throws TransientRenderingException {
        final String message = "transient render failure for " + view;
        log.debug(message, e);
        throw new TransientRenderingException(message, e);
    }

    private void throwRenderingException(Exception e) throws FatalRenderingException, TransientRenderingException {
        Throwable throwable = e;
        while (throwable != null) {
            if (throwable instanceof IllegalStateException || throwable instanceof SessionExpiredException) {
                fatalRenderingException(e);
            } else {
                throwable = throwable.getCause();
            }
        }
        transientRenderingException(e);
    }

    private void failIfDisposed() throws FatalRenderingException {
        if (disposed) {
            //ICE-3073 Clear threadLocal in all paths
            release();
            view.releaseLifecycleLock();
            fatalRenderingException();
        }
    }

    public String getStateRestorationId() {
        return stateRestorationId;
    }

    /**
     * JSF state saving requires a state id token written into the contents of
     * the forms in the client. For server push, however, this field has to be updated each
     * time state is serialized so that the subsequent push operation can artificially
     * insert the id into the request map for JSF code to find.
     *
     * @param stateRestorationId The state id
     */
    public void setStateRestorationId(String stateRestorationId) {
        this.stateRestorationId = stateRestorationId;
    }
}