Package com.daveoxley.cbus

Source Code of com.daveoxley.cbus.CGateSession$PingConnections

/**
*  CGateInterface - A library to allow interaction with Clipsal C-Gate.
*  Copyright (C) 2008,2009,2012  Dave Oxley <dave@daveoxley.co.uk>.
*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU Affero General Public License as
*  published by the Free Software Foundation, either version 3 of the
*  License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Affero General Public License for more details.
*
*  You should have received a copy of the GNU Affero General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package com.daveoxley.cbus;

import com.daveoxley.cbus.events.DebugEventCallback;
import com.daveoxley.cbus.events.EventCallback;
import com.daveoxley.cbus.status.DebugStatusChangeCallback;
import com.daveoxley.cbus.status.StatusChangeCallback;
import com.workplacesystems.utilsj.threadpool.ThreadObjectFactory;
import com.workplacesystems.utilsj.threadpool.ThreadPool;
import com.workplacesystems.utilsj.threadpool.ThreadPoolCreator;
import com.workplacesystems.utilsj.threadpool.WorkerThread;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool.Config;

/**
*
* @author Dave Oxley <dave@daveoxley.co.uk>
*/
public class CGateSession extends CGateObject
{
    private final static Log log = LogFactory.getLog(CGateSession.class);

    private final Map<String,BufferedWriter> response_writers = Collections.synchronizedMap(new HashMap<String,BufferedWriter>());

    private final CommandConnection command_connection;

    private final EventConnection event_connection;

    private final StatusChangeConnection status_change_connection;

    private final PingConnections ping_connections;

    private boolean pingKeepAlive = true;

    private boolean connected = false;

    CGateSession(InetAddress cgate_server, int command_port, int event_port, int status_change_port)
    {
        super(null);
        if (cgate_server == null)
            throw new NullPointerException("cgate_server cannot be null");

        setupSubtreeCache("project");
        command_connection = new CommandConnection(cgate_server, command_port);
        event_connection = new EventConnection(cgate_server, event_port);
        status_change_connection = new StatusChangeConnection(cgate_server, status_change_port);
        registerEventCallback(new DebugEventCallback());
        registerStatusChangeCallback(new DebugStatusChangeCallback());
        ping_connections = new PingConnections();
    }

    @Override
    protected CGateSession getCGateSession()
    {
        return this;
    }

    @Override
    protected String getKey()
    {
        return null;
    }
   
    @Override
    public CGateObject getCGateObject(String address) throws CGateException
    {
        if (!address.startsWith("//"))
            throw new IllegalArgumentException("Address must be a full address. i.e. Starting with //");

        boolean return_next = false;
        int next_part_index = address.indexOf("/", 2);
        if (next_part_index == -1)
        {
            next_part_index = address.length();
            return_next = true;
        }

        String project_name = address.substring(2, next_part_index);
        Project project = Project.getProject(this, project_name);
        if (project == null)
            throw new IllegalArgumentException("No project found: " + address);

        if (return_next)
            return project;

        return project.getCGateObject(address.substring(next_part_index + 1));
    }

    @Override
    String getProjectAddress()
    {
        throw new UnsupportedOperationException();
    }

    @Override
    String getResponseAddress(boolean id)
    {
        throw new UnsupportedOperationException();
    }

    public void connect() throws CGateConnectException
    {
        if (connected)
            return;

        try
        {
            command_connection.start();
            event_connection.start();
            status_change_connection.start();
            connected = true;
            if (pingKeepAlive)
                ping_connections.start();
        }
        catch (CGateConnectException e)
        {
            try
            {
                close();
            }
            catch (Exception e2) {}
            throw e;
        }
    }

    public void enableKeepAlivePing(boolean pingKeepAlive)
    {
        this.pingKeepAlive = pingKeepAlive;
    }

    /**
     * Issue a <code>quit</code> to the C-Gate server and close the input and output stream
     * and the command_socket.
     *
     * @see <a href="http://www.clipsal.com/cis/downloads/Toolkit/CGateServerGuide_1_0.pdf">
     *      <i>C-Gate Server Guide 4.3.99</i></a>
     * @throws com.daveoxley.cbus.CGateException
     */
    public void close() throws CGateException
    {
        if (!connected)
            return;

        synchronized (ping_connections)
        {
            try
            {
                sendCommand("quit").toArray();
            }
            catch (Exception e) {}

            try
            {
                command_connection.stop();
                event_connection.stop();
                status_change_connection.stop();
            }
            catch (Exception e)
            {
                throw new CGateException(e);
            }
            finally
            {
                clearCache();
                connected = false;
                ping_connections.notify();
            }
        }
    }

    /**
     *
     * @param cgate_command
     * @return ArrayList of C-Gate response lines
     * @throws com.daveoxley.cbus.CGateException
     */
    Response sendCommand(String cgate_command) throws CGateException
    {
        checkConnected();

        return command_connection.sendCommand(cgate_command);
    }

    public boolean isConnected()
    {
        return connected;
    }

    private void checkConnected() throws CGateNotConnectedException
    {
        if (!connected)
            throw new CGateNotConnectedException();
        try
        {
            command_connection.start();
            event_connection.start();
            status_change_connection.start();
        }
        catch (CGateConnectException e)
        {
            throw new CGateNotConnectedException();
        }
    }

    private abstract class CGateConnection implements Runnable
    {
        private final InetAddress server;

        private final int port;

        private final boolean create_output;

        private Thread thread = null;

        private Socket socket;

        private volatile BufferedReader input_reader;

        private PrintWriter output_stream;

        protected CGateConnection(InetAddress server, int port, boolean create_output)
        {
            this.server = server;
            this.port = port;
            this.create_output = create_output;
        }

        protected synchronized void start() throws CGateConnectException
        {
            if (thread != null)
                return;

            try
            {
                socket = new Socket(server, port);
                input_reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                if (create_output)
                    output_stream = new PrintWriter(socket.getOutputStream(), true);
                logConnected();

                thread = new Thread(this);
                thread.setDaemon(true);
                thread.start();
            }
            catch (IOException e)
            {
                throw new CGateConnectException(e);
            }
        }

        protected synchronized void stop()
        {
            try
            {
                thread = null;

                // Only close the Socket as trying to close the BufferedReader results
                // in a deadlock (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4859836).
                try
                {
                    if (socket != null)
                        socket.close();
                }
                catch (IOException e)
                {
                    new CGateException(e);
                }
            }
            catch (Exception e)
            {
                new CGateException(e);
            }
            finally
            {
                input_reader = null;
                output_stream = null;
                socket = null;
            }
        }

        public void println(String str) throws CGateException
        {
            if (!create_output || output_stream == null)
                throw new CGateException();

            output_stream.println(str);
            output_stream.flush();
        }

        protected final BufferedReader getInputReader()
        {
            return input_reader;
        }

        protected void logConnected() throws IOException {}

        protected synchronized boolean continueRunning()
        {
            return thread != null;
        }

        public final void run()
        {
            try
            {
                while (continueRunning())
                {
                    doRun();
                }
            }
            catch (IOException ioe)
            {
                if (thread != null)
                    new CGateException(ioe);
            }
            catch (Exception e)
            {
                new CGateException(e);
            }
            finally
            {
                boolean restart = thread != null;
                stop();
                if (restart)
                {
                    try
                    {
                        start();
                    }
                    catch (CGateConnectException e) {}
                }
            }
        }

        protected abstract void doRun() throws IOException;
    }

    private class PingConnections implements Runnable
    {
        private Thread thread;

        private PingConnections()
        {
        }

        protected void start()
        {
            thread = new Thread(this);
            thread.setDaemon(true);
            thread.start();
        }

        public synchronized void run()
        {
            try
            {
                while (connected && pingKeepAlive)
                {
                    try
                    {
                        try
                        {
                            wait(10000l);
                        }
                        catch (InterruptedException e) {}

                        if (connected && pingKeepAlive)
                            CGateInterface.noop(CGateSession.this);
                    }
                    catch (Exception e) {}
                }
            }
            finally
            {
                thread = null;
            }
        }
    }

    private BufferedReader getReader(String id) throws CGateException
    {
        try
        {
            PipedWriter piped_writer = new PipedWriter();
            BufferedWriter out = new BufferedWriter(piped_writer);
            response_writers.put(id, out);

            PipedReader piped_reader = new PipedReader(piped_writer);
            return new BufferedReader(piped_reader);
        }
        catch(IOException e)
        {
            throw new CGateException(e);
        }
    }

    private class CommandConnection extends CGateConnection
    {
        private int next_id = 0;

        private CommandConnection(InetAddress server, int port)
        {
            super(server, port, true);
        }

        private Response sendCommand(String cgate_command) throws CGateException
        {
            String id = getID();
            BufferedReader response_reader = getReader(id);

            command_connection.println("[" + id + "] " + cgate_command);

            return new Response(response_reader);
        }

        private synchronized String getID()
        {
            return String.valueOf(next_id++);
        }

        @Override
        protected void logConnected() throws IOException
        {
            log.debug(getInputReader().readLine());
        }

        @Override
        public void doRun() throws IOException
        {
            final String response = getInputReader().readLine();
            if (response == null)
                super.stop();
            else
            {
                int id_end = response.indexOf("]");
                String id = response.substring(1, id_end);
                String actual_response = response.substring(id_end + 2);

                BufferedWriter writer = response_writers.get(id);
                writer.write(actual_response);
                writer.newLine();

                if (!Response.responseHasMore(actual_response))
                {
                    writer.flush();
                    writer.close();
                    response_writers.remove(id);
                }
            }
        }
    }

    /**
     *
     * @param event_callback
     */
    public void registerEventCallback(EventCallback event_callback)
    {
        event_connection.registerEventCallback(event_callback);
    }

    private class EventConnection extends CGateConnection
    {
        private final ThreadPool event_callback_pool;

        private final List<EventCallback> event_callbacks = Collections.synchronizedList(new ArrayList<EventCallback>());

        private EventConnection(InetAddress server, int port)
        {
            super(server, port, false);
            ThreadPoolCreator tp_creator = new ThreadPoolCreator() {

                public ThreadObjectFactory getThreadObjectFactory() {
                    return new ThreadObjectFactory() {
                        @Override
                        public void initialiseThread(Thread thread)
                        {
                            thread.setName("Event");
                        }

                        @Override
                        public void activateThread(Thread thread)
                        {
                        }

                        @Override
                        public void passivateThread(Thread thread)
                        {
                        }
                    };
                }

                public Config getThreadPoolConfig() {
                    Config config = new Config();
                    config.maxActive = 10;
                    config.minIdle   = 2;
                    config.maxIdle   = 5;
                    config.testOnBorrow = false;
                    config.testOnReturn = true;
                    config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
                    config.maxWait = -1;
                    return config;
                }

                public String getThreadPoolName() {
                    return "EventPool";
                }
            };
            event_callback_pool = new ThreadPool(tp_creator.getThreadObjectFactory(), tp_creator.getThreadPoolConfig());
        }

        private void registerEventCallback(EventCallback event_callback)
        {
            event_callbacks.add(event_callback);
        }

        @Override
        protected void doRun() throws IOException
        {
            final String event = getInputReader().readLine();
            if (event == null)
                super.stop();
            else if (event.length() >= 19)
            {
                final int event_code = Integer.parseInt(event.substring(16, 19).trim());
                for (final EventCallback event_callback : event_callbacks)
                {
                    if (!continueRunning())
                        return;

                    try
                    {
                        if (event_callback.acceptEvent(event_code))
                        {
                            WorkerThread callback_thread = (WorkerThread)event_callback_pool.borrowObject();
                            callback_thread.execute(new Runnable() {
                                public void run()
                                {
                                    GregorianCalendar event_time = new GregorianCalendar(
                                            Integer.parseInt(event.substring(0, 4)),
                                            Integer.parseInt(event.substring(4, 6)),
                                            Integer.parseInt(event.substring(6, 8)),
                                            Integer.parseInt(event.substring(9, 11)),
                                            Integer.parseInt(event.substring(11, 13)),
                                            Integer.parseInt(event.substring(13, 15)));

                                    event_callback.processEvent(CGateSession.this, event_code,
                                            event_time, event.length() == 19 ? null : event.substring(19));
                                }
                            }, null);
                        }
                    }
                    catch (Exception e)
                    {
                        new CGateException(e);
                    }
                }
            }
        }
    }

    /**
     *
     * @param event_callback
     */
    public void registerStatusChangeCallback(StatusChangeCallback status_change_callback)
    {
        status_change_connection.registerStatusChangeCallback(status_change_callback);
    }

    private class StatusChangeConnection extends CGateConnection
    {
        private final ThreadPool sc_callback_pool;

        private final List<StatusChangeCallback> sc_callbacks = Collections.synchronizedList(new ArrayList<StatusChangeCallback>());

        private StatusChangeConnection(InetAddress server, int port)
        {
            super(server, port, false);
            ThreadPoolCreator tp_creator = new ThreadPoolCreator() {

                public ThreadObjectFactory getThreadObjectFactory() {
                    return new ThreadObjectFactory() {
                        @Override
                        public void initialiseThread(Thread thread)
                        {
                            thread.setName("StatusChange");
                        }

                        @Override
                        public void activateThread(Thread thread)
                        {
                        }

                        @Override
                        public void passivateThread(Thread thread)
                        {
                        }
                    };
                }

                public Config getThreadPoolConfig() {
                    Config config = new Config();
                    config.maxActive = 10;
                    config.minIdle   = 2;
                    config.maxIdle   = 5;
                    config.testOnBorrow = false;
                    config.testOnReturn = true;
                    config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
                    config.maxWait = -1;
                    return config;
                }

                public String getThreadPoolName() {
                    return "StatusChangePool";
                }
            };
            sc_callback_pool = new ThreadPool(tp_creator.getThreadObjectFactory(), tp_creator.getThreadPoolConfig());
        }

        private void registerStatusChangeCallback(StatusChangeCallback event_callback)
        {
            sc_callbacks.add(event_callback);
        }

        @Override
        protected void doRun() throws IOException
        {
            final String status_change = getInputReader().readLine();
            if (status_change == null)
                super.stop();
            else if (status_change.length() > 0)
            {
                for (final StatusChangeCallback sc_callback : sc_callbacks)
                {
                    if (!continueRunning())
                        return;

                    if (sc_callback.isActive())
                    {
                        try
                        {
                            WorkerThread callback_thread = (WorkerThread)sc_callback_pool.borrowObject();
                            callback_thread.execute(new Runnable() {
                                public void run()
                                {
                                    sc_callback.processStatusChange(CGateSession.this, status_change);
                                }
                            }, null);
                        }
                        catch (Exception e)
                        {
                            new CGateException(e);
                        }
                    }
                }
            }
        }
    }
}
TOP

Related Classes of com.daveoxley.cbus.CGateSession$PingConnections

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.