org.gwtwidgets.client.stream.HtmlStreamReader.java Source code

Java tutorial

Introduction

Here is the source code for org.gwtwidgets.client.stream.HtmlStreamReader.java

Source

/*
 * Licensed 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.gwtwidgets.client.stream;

import java.util.Date;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.IFrameElement;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Timer;

/**
 * Decodes SL stream protocol messages sent via the
 * {@link HtmlSLStreamWriterImpl}. This implementation creates an invisible
 * IFrame which is appended to the current document and polled in intervals.
 * Decoded messages are then removed from the DOM in order to reduce resource
 * consumption.
 * 
 * @author George Georgovassilis, g.georgovassilis[at]gmail.com
 * 
 */

public class HtmlStreamReader {

    protected IFrameElement frame;
    protected long pollingInterval = 1000;
    protected long maxAge = 5 * 60 * 1000;
    protected Timer timer;
    protected Date dateOfCreation;
    protected int lastMessageId = -1;

    private boolean isObsolete() {
        Date now = new Date();
        long diff = now.getTime() - dateOfCreation.getTime();
        return diff > maxAge;
    }

    //workaround until GWT #2805 is released
    protected native Document getFrameContents(IFrameElement frame)/*-{
                                                                   return frame.contentWindow.document; 
                                                                   }-*/;

    private Message decodeMessage(Element e) {
        Message message = new Message();
        String content = e.getInnerText();
        String[] parts = content.split(",");
        message.setContent(URL.decodeQueryString(parts[0]));
        for (int i = 1; i < parts.length; i++) {
            String[] keyValuePair = parts[i].split("=");
            message.getAttributes().put(URL.decodeQueryString(keyValuePair[0]),
                    URL.decodeQueryString(keyValuePair[1]));
        }
        message.setSerialNumber(Integer.parseInt(e.getAttribute("id")));
        return message;
    }

    private Message checkForEOF(Document document) {
        NodeList<Element> eSpans = document.getElementsByTagName("span");
        if (eSpans.getLength() == 0)
            return null;
        Element eSpan = eSpans.getItem(0);
        if ("EOF".equals(eSpan.getInnerText())) {
            Message message = new Message();
            message.setEOF(true);
            return message;
        }
        return null;
    }

    protected Message readNextMessage() {
        Document document = getFrameContents(frame);
        if (null == document)
            return null;
        NodeList<Element> elements = document.getElementsByTagName("div");
        if (elements.getLength() == 0) {
            return checkForEOF(document);
        }
        Element eDiv = elements.getItem(0);
        eDiv.getParentElement().removeChild(eDiv);
        Message message = decodeMessage(eDiv);
        lastMessageId = message.getSerialNumber();
        return message;
    };

    public long getPollingInterval() {
        return pollingInterval;
    }

    /**
     * Sets a polling interval - messages arriving from the server will be
     * decoded every such interval. Please note that this parameter is passed to
     * {@link Timer#schedule(int)} which receives an integer argument; hence
     * large values for <code>pollingInterval</code> will not yield the
     * desired behavior.
     * 
     * @param pollingInterval
     *            Polling interval in milliseconds
     */
    public void setPollingInterval(long pollingInterval) {
        this.pollingInterval = pollingInterval;
    }

    protected Element getFrame() {
        return frame;
    }

    /**
     * For debugging purposes, can be used to show the IFrame used to receive
     * server messages.
     * 
     * @param status
     */
    public void setInnerFrameVisibility(boolean status) {
        //      frame.getStyle().setProperty("visibility", status?"visible":"hidden");
    }

    public HtmlStreamReader() {
    }

    /**
     * Connects to a URL an starts streaming messages from there. Note that
     * despite this being an asynchronous message, the {@link HtmlStreamReader}
     * is a stateful object which cannot be used to simultaneously access more
     * resources.
     * 
     * @param url
     * @param callback
     */
    public void readAsync(final String url, final StreamCallback callback) {
        GWT.log("Openeing stream to " + url, null);
        final String sUrl = url + (url.contains("&") ? "&" : "?") + "lastmessageid=" + lastMessageId;
        dateOfCreation = new Date();
        frame = Document.get().createIFrameElement();
        setInnerFrameVisibility(false);
        Document.get().getBody().appendChild(frame);
        frame.setSrc(sUrl);
        timer = new Timer() {
            @Override
            public void run() {
                try {
                    Message message = null;
                    while (null != (message = readNextMessage())) {
                        if (message.isEOF()) {
                            close();
                            callback.onDisconnect();
                            return;
                        }
                        callback.onMessage(message);
                    }
                    if (isObsolete()) {
                        reconnect(url, callback);
                        return;
                    }
                    timer.schedule((int) getPollingInterval());
                } catch (Exception e) {
                    close();
                    callback.onError(e);
                }
            }
        };
        timer.schedule((int) getPollingInterval());
    }

    protected void reconnect(String url, StreamCallback callback) {
        GWT.log("Refreshing connection to server with id " + lastMessageId, null);
        close();
        readAsync(url, callback);
    }

    /**
     * Should be called if the client wants to close the stream communication
     * with the server.
     */
    public void close() {
        frame.setSrc("");
        timer.cancel();
        frame.getParentElement().removeChild(frame);
        frame = null;
    }

    /**
     * Determines the age (in ms) of a connection to a server before closing and refreshing it
     * @param maxAge
     */
    public void setMaxAge(long maxAge) {
        this.maxAge = maxAge;
    }
}