Java tutorial
/* * CGateInterface - A library to allow interaction with Clipsal C-Gate. * * Copyright 2008, 2009, 2012, 2017 Dave Oxley <dave@daveoxley.co.uk> * * 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.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); } } } } } } }