com.icesoft.faces.async.render.GroupAsyncRenderer.java Source code

Java tutorial

Introduction

Here is the source code for com.icesoft.faces.async.render.GroupAsyncRenderer.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.async.render;

import com.icesoft.faces.context.View;
import com.icesoft.faces.webapp.http.servlet.MainSessionBoundServlet;
import com.icesoft.faces.webapp.http.servlet.SessionDispatcher;
import com.icesoft.faces.webapp.http.servlet.PseudoServlet;
import com.icesoft.faces.webapp.http.common.Server;
import com.icesoft.faces.webapp.xmlhttp.PersistentFacesState;
import com.icesoft.faces.webapp.xmlhttp.RenderingException;
import com.icesoft.util.StaticTimerUtility;
import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.faces.context.FacesContext;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Set;

/**
 * The GroupAsyncRenderer is the foundation class for other types of renderers
 * that are designed to operate on a group of {@link Renderable}s.  It
 * implements the {@link AsyncRenderer} interface and is mainly responsible for
 * smartly managing a group of Renderable instances.
 * <p/>
 * Groups of Renderables are stored as WeakReferences in special sets that are
 * copied before each render pass so that Renderables can be safely added and
 * removed from the group while a render pass is in progress.
 * <p/>
 * Although it is possible to create and use GroupRenderers directly, developers
 * are advised to use the {@link RenderManager} to create and use named render
 * groups.
 *
 * @author ICEsoft Technologies, Inc.
 * @see RenderManager, OnDemandRenderer, IntervalRenderer, DelayRenderer
 */
public class GroupAsyncRenderer implements AsyncRenderer {
    private static final Log LOG = LogFactory.getLog(GroupAsyncRenderer.class);

    protected final Set group = new CopyOnWriteArraySet();

    protected boolean broadcasted = false;
    protected String name;

    protected boolean stopRequested = false;

    public GroupAsyncRenderer() {
    }

    /**
     * <p>
     *   Adds the specified <code>renderable</code>, via a
     *   <code>WeakReference</code>, to the set of <code>Renderable</code>s of
     *   this group.  If it is already in this set, it is not added again.
     * </p>
     *
     * @param      renderable
     *                 the Renderable instance to add to the group.
     * @throws     IllegalArgumentException
     *                 if the specified <code>renderable</code> is
     *                 <code>null</code>.
     */
    public void add(final Renderable renderable) throws IllegalArgumentException {
        if (renderable != null) {
            add((Object) renderable);
        } else {
            throw new IllegalArgumentException("renderable is null");
        }
    }

    /**
     * <p>
     *   Adds the current session, via a <code>WeakReference</code>, to this
     *   <code>GroupAsyncRenderer</code>.  If this already contains the current
     *   session, it is not added again.
     * </p>
     *
     * @throws     IllegalStateException
     *                 if no current session is active.
     */
    public void addCurrentSession() throws IllegalStateException {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext != null) {
            Object session = facesContext.getExternalContext().getSession(false);
            if (session != null) {
                add(getSessionID(session));
            } else {
                LOG.warn("Unable to add current session: " + "no current session active");
                throw new IllegalStateException("Unable to add current session: " + "no current session active");
            }
        } else {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Add current session cannot be done from non-JSF thread. "
                        + "Failed to add session to group '" + name + "'.");
            }
        }
    }

    /**
     * Removes all Renderables from the group.
     */
    public void clear() {
        // todo: remove synchronized block as CopyOnWriteArraySet is used?
        synchronized (group) {
            group.clear();
        }
    }

    public boolean contains(final Renderable renderable) {
        return contains((Object) renderable);
    }

    public boolean containsCurrentSession() {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext == null) {
            return false;
        }

        return contains(getSessionID(facesContext.getExternalContext().getSession(false)));
    }

    private static String getSessionID(Object session) {
        if (session == null) {
            return null;
        }

        try {
            Method getIdMethod = session.getClass().getMethod("getId", null);
            Object id = getIdMethod.invoke(session, null);
            return id.toString();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Remove all Renderables from the group and removes the reference to the
     * RenderHub.  Once disposed, a GroupAsyncRenderer cannot be re-used.  This
     * method is typically used by the RenderManager to cleanly dispose of all
     * managed Renderers when the application is shutting down.
     */
    public void dispose() {
        requestStop();
        RenderManager.getInstance().removeRenderer(this);
        clear();
        name = null;
    }

    public String getName() {
        return name;
    }

    public boolean isBroadcasted() {
        return broadcasted;
    }

    /**
     * Used to determine if the Renderer has any Renderables left in its
     * collection.
     *
     * @return false if there are 1 or more Renderables left in the Renderer's
     *         collection, true otherwise
     */
    public boolean isEmpty() {
        return group.isEmpty();
    }

    /**
     * Removes a Renderable, via a WeakReference, from the set of Renderables of
     * this group.
     *
     * @param renderable the Renderable instance to remove
     */
    public void remove(final Renderable renderable) {
        remove((Object) renderable);
    }

    /**
     * <p>
     *   Removes the current session, via a <code>WeakReference</code>, from
     *   this <code>GroupAsyncRenderer</code>.
     * </p>
     */
    public void removeCurrentSession() {
        LOG.info("GroupAsyncRenderer.removeCurrentSession()");
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext != null) {
            Object session = facesContext.getExternalContext().getSession(false);
            if (session != null) {
                remove(getSessionID(session));
            } else {
                LOG.warn("Unable to remove current session: " + "no current session active");
            }
        }
    }

    String lastRenderInfo = "";

    public String getLastRenderInfo() {
        return lastRenderInfo;
    }

    /**
     * Request a render pass on all the Renderables in the group.  Render calls
     * that generate exceptions are passed back to the Renderable.renderException
     *
     * @throws IllegalStateException If a reference to a {@link RenderHub} has
     *                               not yet been set.
     */
    public void requestRender() {
        requestRender(true);
    }

    public void requestRender(final boolean allowBroadcasting) {
        if (LOG.isTraceEnabled()) {
            LOG.trace(name + " preparing to render " + group.size());
        }
        lastRenderInfo = "groupSize=" + group.size() + " startTime=" + System.currentTimeMillis();
        if (allowBroadcasting && isBroadcasted()) {
            // allow for potential broadcasting
            RenderManager.getInstance().requestRender(this);
        }
        stopRequested = false;
        /*
         * Note that the Iterator returned by the CopyOnWriteArraySet relies on
         * an unchanging snapshot of the array at the time the Iterator was
         * constructed and does not support the mutative remove operation!
         */
        if (StaticTimerUtility.Log.isTraceEnabled()) {
            StaticTimerUtility.newJob(group.size());
            StaticTimerUtility.startJobTmer();
        }
        /*
         * Invocation from a non-JSF thread currently cannot invoke a render
         * on sessions.
         */
        for (Iterator i = group.iterator(); !stopRequested && i.hasNext();) {
            /*
             * From the CopyOnWriteArraySet:
             *
             *     "The returned iterator provides a snapshot of the state of
             *     the set when the iterator was constructed.  No
             *     synchronization is needed while traversing the iterator.  The
             *     iterator does NOT support the remove method."
             */
            WeakReference reference = (WeakReference) i.next();
            Object object = reference.get();
            if (object == null) {
                group.remove(reference);
            } else if (object instanceof Renderable) {
                requestRender((Renderable) object);
            } else if (object instanceof String) {
                String sessionId = (String) object;
                try {
                    requestRender(sessionId);
                } catch (Exception exception) {
                    /*
                     * Remove from the CopyOnWriteArraySet is allowed here as
                     * the Iterator in requestRender(boolean) relies on an
                     * unchanging snapshot of the array at the time the Iterator
                     * was constructed.
                     */
                    remove(sessionId);
                }
            }
        }
    }

    /**
     * The method called by dispose to halt a render pass at the current {@link
     * Renderable}s.
     */
    public void requestStop() {
        stopRequested = true;
    }

    public void setBroadcasted(final boolean broadcasted) {
        this.broadcasted = broadcasted;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public void setRenderManager(final RenderManager renderManager) {
        // do nothing.
    }

    private void add(final Object object) {
        // todo: remove synchronized block as CopyOnWriteArraySet is used?
        synchronized (group) {
            if (!contains(object)) {
                if (group.add(new WeakReference(object))) {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace(name + " added " + object);
                    }
                } else {
                    if (LOG.isWarnEnabled()) {
                        LOG.warn(name + " already contains " + object);
                    }
                }
            }
        }
    }

    private boolean contains(final Object object) {
        for (Iterator i = group.iterator(); i.hasNext();) {
            if (object == ((WeakReference) i.next()).get()) {
                return true;
            }
        }
        return false;
    }

    private void remove(final Object object) {
        // todo: remove synchronized block as CopyOnWriteArraySet is used?
        synchronized (group) {
            for (Iterator i = group.iterator(); i.hasNext();) {
                WeakReference reference = (WeakReference) i.next();
                if (object == reference.get()) {
                    group.remove(reference);
                    if (LOG.isTraceEnabled()) {
                        LOG.trace(name + " removing " + object);
                    }
                    return;
                }
            }
            if (LOG.isWarnEnabled()) {
                LOG.warn(name + " does not contain " + object);
            }
        }
    }

    private void requestRender(final String sessionId) {
        PersistentFacesState suppressedViewState;
        if (FacesContext.getCurrentInstance() != null) {
            suppressedViewState = PersistentFacesState.getInstance();
        } else {
            /*
             * Invocation from a non-JSF thread should not suppress the
             * current view.
             */
            suppressedViewState = null;
        }

        PseudoServlet serv = SessionDispatcher.getSingletonSessionServer(sessionId,
                RenderManager.getInstance().getServletContext());
        if (serv == null) {
            group.remove(sessionId);
            return;
        }

        for (Iterator i = ((MainSessionBoundServlet) serv).getViews().values().iterator(); i.hasNext();) {

            final PersistentFacesState viewState = ((View) i.next()).getPersistentFacesState();
            if (viewState != suppressedViewState) {
                requestRender(new Renderable() {
                    public PersistentFacesState getState() {
                        return viewState;
                    }

                    public void renderingException(final RenderingException renderingException) {

                        /*
                         * It's up to our View infrastructure to remove
                         * dead views.
                         */
                    }
                });
            }
        }
    }

    private void requestRender(final Renderable renderable) {
        RenderManager.getInstance().requestRender(renderable);
    }
}