All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.directwebremoting.impl.BaseSleeper Maven / Gradle / Ivy

package org.directwebremoting.impl;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.ConversionException;
import org.directwebremoting.ScriptBuffer;
import org.directwebremoting.extend.ConverterManager;
import org.directwebremoting.extend.EnginePrivate;
import org.directwebremoting.extend.RealScriptSession;
import org.directwebremoting.extend.ScriptBufferUtil;
import org.directwebremoting.extend.ScriptConduit;
import org.directwebremoting.extend.Sleeper;
import org.directwebremoting.util.LocalUtil;

/**
 * Common functionality for stock Sleepers.
 * @author Mike Wilson
 */
public abstract class BaseSleeper implements Sleeper
{
    public BaseSleeper(HttpServletResponse response, RealScriptSession scriptSession, ScriptConduit conduit) throws IOException
    {
        this.response = response;
        this.scriptSession = scriptSession;
        this.conduit = conduit;

        out = response.getWriter();
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.Sleeper#enterSleep(java.lang.Runnable, int)
     */
    @SuppressWarnings("hiding")
    public final void enterSleep(String batchId, Runnable onClose, int disconnectedTime)
    {
        this.batchId = batchId;
        this.onClose = onClose;
        this.disconnectedTime = disconnectedTime;
        // Send beginStream and execute Runnables in original request thread before sleeping
        doWork();
        if (!closed)
        {
            enterSleep();
        }
    }

    /**
     * Should "halt" the current poll request while allowing output be written to its response.
     * (abstract method to be implemented by concrete Sleepers)
     */
    protected abstract void enterSleep();

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.Sleeper#wakeUpForData()
     */
    public final void wakeUpForData()
    {
        wakeUp();
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.Sleeper#wakeUpToClose()
     */
    public final int wakeUpToClose()
    {
        synchronized (lock) {
            closePending = true;
        }
        wakeUp();
        return disconnectedTime;
    }

    /**
     * Should cause the doWork() method to be called on a background or
     * container thread.
     * (abstract method to be implemented by concrete Sleepers)
     */
    protected abstract void wakeUp();

    /**
     * This method should be called upon by the concrete Sleepers when they
     * are awakened by the wakeUp() call. They should guarantee that the call
     * is made on a container or background thread (thus not blocking the
     * wakeUp() call) and that it is invoked by no more than a single thread
     * at a time.
     */
    protected final void doWork()
    {
        try {
            if (closed) {
                return;
            }
            synchronized (lock) {
                if (closePending) {
                    closePending = false;
                    closed = true;
                }
            }
            if (!opened) {
                beginStreamAndChunk();
                opened = true;
            }
            sendNewChunkScripts();
            checkNonChunkScripts();
            if (closed) {
                sendPollReply();
                endStreamAndChunk();
                doClose();
            } else {
                endChunk();
            }

            // If we got here without exceptions then writing was ok so we can update alive timestamp
            scriptSession.updateLastAccessedTime();
        } catch(Exception ex) {
            if (LocalUtil.getRootCause(ex) instanceof IOException) {
                log.debug("Poll I/O error", ex);
            } else {
                log.error("Error during poll.", ex);
            }
            closed = true;
            try {
                sendPollReply();
                endStreamAndChunk();
            } catch(Exception ignore) {
                // Swallow any exception and fall through to attempt to close poll below
            }
            doClose();
        }
    }

    private void beginStreamAndChunk() throws Exception
    {
        response.setContentType(conduit.getOutboundMimeType());
        // If there is a runnable for beginning of response then run it
        RealScriptSession.Script script = scriptSession.getScript(nextScriptIndex);
        boolean beginningRunnable = false;
        if (script != null && script.getScript() instanceof Runnable) {
            try {
                ((Runnable) script.getScript()).run();
            } catch(Exception ex) {
                log.error("Exception when executing Script Runnable.", ex);
            }
            beginningRunnable = true;
        }
        // Send stream prefix
        conduit.beginStreamAndChunk();
        // Send confirmation for the runnable to client after stream prefix
        if (beginningRunnable) {
            conduit.sendScript(EnginePrivate.getRemoteHandleReverseAjaxScript(script.getIndex(), ""));
            nextScriptIndex = script.getIndex() + 1;
        }
    }

    private void sendNewChunkScripts() throws Exception
    {
        // Scripts
        while(true) {
            RealScriptSession.Script script = scriptSession.getScript(nextScriptIndex);
            if (script != null && script.getScript() instanceof String) {
                conduit.sendScript(EnginePrivate.getRemoteHandleReverseAjaxScript(script.getIndex(), (String) script.getScript()));
                nextScriptIndex = script.getIndex() + 1;
            } else {
                break;
            }
        }
    }

    private void checkNonChunkScripts()
    {
        RealScriptSession.Script script = scriptSession.getScript(nextScriptIndex);
        if (script != null && !(script.getScript() instanceof String)) {
            // Trigger a new poll request without waiting so non-chunk scripts can execute
            closed = true;
            disconnectedTime = 0;
        }
    }

    private void sendPollReply() throws ConversionException, IOException
    {
        ScriptBuffer script = EnginePrivate.getRemoteHandleCallbackScript(batchId, "0", disconnectedTime);
        conduit.sendScript(ScriptBufferUtil.createOutput(script, converterManager, jsonOutput));
    }

    private void endStreamAndChunk() throws Exception
    {
        conduit.endStreamAndChunk();
    }

    private void endChunk() throws IOException
    {
        conduit.endChunk();

        // Flush and check errors
        out.flush();
        if (out.checkError())
        {
            throw new IOException("Stream write error");
        }
        response.flushBuffer();
    }

    private void doClose()
    {
        if (onClose != null)
        {
            onClose.run();
        }
        close();
    }

    public void setConverterManager(ConverterManager converterManager)
    {
        this.converterManager = converterManager;
    }

    public void setJsonOutput(boolean jsonOutput)
    {
        this.jsonOutput = jsonOutput;
    }

    /**
     * Should close the ongoing poll request and end any asynchronous cycle.
     * (abstract method to be implemented by concrete Sleepers)
     */
    protected abstract void close();

    // Members set in constructor
    private final HttpServletResponse response;
    private final RealScriptSession scriptSession;
    private final ScriptConduit conduit;
    private final PrintWriter out;

    // Members set in enterSleep
    private String batchId;
    private Runnable onClose;
    private int disconnectedTime;

    // Injected from container
    protected ConverterManager converterManager = null;
    protected boolean jsonOutput = false;

    // State manipulated by calls on public API
    private final Object lock = new Object();
    private boolean closePending = false;

    // State manipulated by (thread-safe) internal calls from sub-classes
    private boolean opened = false;
    private boolean closed = false;
    private long nextScriptIndex = 0;

    /**
     * The log stream
     */
    private static final Log log = LogFactory.getLog(BaseSleeper.class);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy