com.appenginefan.toolkit.common.WebConnectionServer.java Source code

Java tutorial

Introduction

Here is the source code for com.appenginefan.toolkit.common.WebConnectionServer.java

Source

/*
 * Copyright (c) 2009 Jens Scheffler (appenginefan.com)
 * 
 * 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 com.appenginefan.toolkit.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.appenginefan.toolkit.persistence.Persistence;
import com.google.common.base.Preconditions;

/**
 * Manages the peristence of server-side endpoints for a WebConnectionClient,
 * and the execution of incoming requests.
 *  
 * @author Jens Scheffler
 *
 */
public class WebConnectionServer {

    private static final Logger LOG = Logger.getLogger(WebConnectionServer.class.getName());

    /**
     * Represents the receiving end of a communication
     */
    public static interface Receiver {

        public void onEmptyPayload(WebConnectionServer server, ServerEndpoint endpoint, HttpServletRequest req);

        public void receive(WebConnectionServer server, ServerEndpoint endpoint, String message,
                HttpServletRequest req);
    }

    /**
     * Represents the inner works of a server. An instance of this interface is 
     * contained within a WebConnectionServer, which uses its different methods during
     * dispatch.
     */
    public static interface ServerGuts {

        /**
         * Assumed a given request belongs to an existing ServerEndpoint, loads
         * that object. It is assumed that the ServerEndpoint is in its connected 
         * state. Unlike newServerEndpoint(), an existing ServerEndpoint object may have
         * additional state in a database. This method is expected to load
         * that data, as well.
         * @param request the request that triggers the lookup
         * @param meta the meta-tag from the http request
         * @return a ServerEndpoint object if existent, or null if nothing is found
         */
        abstract ServerEndpoint loadServerEndpoint(HttpServletRequest request, String meta);

        /**
         * For the given HttpServletRequest, create a new server endpoint.
         * This is the equivalent of a newly opened socket connection.
         * @param request the request that triggers the creation
         * @return a non-null ServerEndpoint object that is not "open" yet
         */
        abstract ServerEndpoint newServerEndpoint(HttpServletRequest request);

        /**
         * Populate the cookies in a servlet response with whatever data is needed
         * to support the loadServerEndpoint() method in future requests. Also, write the
         * body of the response.
         * @param bus the ServerEndpoint to transmit
         * @param response the response to populate
         */
        abstract void writeState(HttpServletRequest request, ServerEndpoint bus, HttpServletResponse response)
                throws IOException;

        /**
         * Converts a ServerEndpoint to a unique handle that can be used to persist
         * inner state in a data store
         * @param bus a non-null ServerEndpoint
         * @return the unique id of the bus
         */
        abstract String toHandle(ServerEndpoint bus);

        /**
         * Loads a ServerEndpoint object from a given handle. The ServerEndpoint object is
         * usually less "complete" than an object constructed from a request, since it
         * does not contain the state stored in the http client, but it is sufficient
         * to perform certain manipulattions, like enqueuing outgoing messages.
         * @param handle the handle that should be used for constructing the object
         * @return a ServerEndpoint object, or null if the handle is invalid
         */
        abstract ServerEndpoint fromHandle(HttpServletRequest request, String handle);

        /**
         * Commits any changes made to the store in this thread that have not been written yet.
         */
        abstract void commit(HttpServletRequest request);

        /**
         * Commits any changes made to the store in this thread that have not been written yet.
         */
        abstract void rollback(HttpServletRequest request);
    }

    private final ServerGuts server;

    /**
     * Constructor
     * @param server the server guts being wrapped. Must not be null.
     */
    public WebConnectionServer(ServerGuts server) {
        Preconditions.checkNotNull(server);
        this.server = server;
    }

    /**
     * Factory-method; creates a server based on a binary store
     * @param persistence the storage algorithm for binary data
     * @return a fully configured server
     */
    public static WebConnectionServer fromPeristence(Persistence<byte[]> persistence) {
        return new WebConnectionServer(new WebConnectionServerImpl(persistence));
    }

    /**
     * Dispatches an incoming request. This method can be called from a servlet
     * that wraps this object.
     * @throws IOException
     * @return true if the incoming body was valid and the message has been processed
     */
    public final boolean dispatch(Receiver receiver, HttpServletRequest req, HttpServletResponse resp)
            throws IOException {

        // First, read the content of the request into a string
        BufferedReader reader = req.getReader();
        String body = null;
        try {
            StringBuilder sb = new StringBuilder();
            for (int c = reader.read(); c >= 0; c = reader.read()) {
                sb.append((char) c);
            }
            body = sb.toString();
        } finally {
            reader.close();
        }

        // Can we parse the body?
        String meta = null;
        List<String> messages = null;
        try {

            // Is it valid JSON?
            JSONObject payload = null;
            payload = new JSONObject(body);

            // Does it have a meta tag and an array of messages?
            if (payload.has(WebConnectionClient.META)) {
                meta = payload.getString(WebConnectionClient.META);
            }
            JSONArray array = payload.getJSONArray(PayloadBuilder.TAG);

            // Extract all the messages out of the array
            messages = new ArrayList<String>();
            for (int i = 0; i < array.length(); i++) {
                messages.add(array.getString(i));
            }
        } catch (JSONException e) {
            return false;
        }

        boolean success = false;

        try {
            // New or existing connection?
            ServerEndpoint bus = null;
            boolean newBus = false;
            if (meta != null) {
                bus = server.loadServerEndpoint(req, meta);
            }
            if (bus == null) {
                bus = server.newServerEndpoint(req);
                newBus = true;
            }
            if (newBus) {
                bus.open();
            }

            // Process the payload
            if (!newBus) {
                if (messages.isEmpty()) {
                    try {
                        receiver.onEmptyPayload(this, bus, req);
                    } catch (Throwable t) {
                        LOG.log(Level.WARNING, "Error while processing empty message", t);
                    }
                } else {
                    for (String message : messages) {
                        try {
                            receiver.receive(this, bus, message, req);
                        } catch (Throwable t) {
                            LOG.log(Level.WARNING, "Error while processing: " + message, t);
                        }
                    }
                }
            }

            // Now, put some data into the response and return
            server.writeState(req, bus, resp);
            success = true;

        } finally {

            // Commit all changes to the store, or roll them back
            if (success) {
                server.commit(req);
            } else {
                server.rollback(req);
            }
        }

        // Done
        return true;
    }

    /**
     * Loads a ServerEndpoint object from a given handle. The ServerEndpoint object is
     * usually less "complete" than an object constructed from a request, since it
     * does not contain the state stored in the http client, but it is sufficient
     * to perform certain manipulations, like enqueuing outgoing messages.
     * @param handle the handle that should be used for constructing the object
     * @return a ServerEndpoint object, or null if the handle is invalid
     */
    public ServerEndpoint fromHandle(HttpServletRequest request, String handle) {
        return server.fromHandle(request, handle);
    }

}