/* * JUnique - Helps in preventing multiple instances of the same application * * Copyright (C) 2008-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version * 2.1, as published by the Free Software Foundation. * * 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 Lesser General Public License 2.1 for more details. * * You should have received a copy of the GNU Lesser General Public * License version 2.1 along with this program. * If not, see <http://www.gnu.org/licenses/>. */ package it.sauronsoftware.junique; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.ArrayList; /** * A LockServer is started every time a lock has been taken by a caller. A * LockServer listens on a local port for messages sent on the lock channel. * Once a message is received the server delivers it to the * {@link MessageHandler} supplied by the caller. * * @author Carlo Pelliccia */ class Server implements Runnable, ConnectionListener { /** * The lock id. */ private String id; /** * The message handler used by this server. It could be null if who has * taken the lock is not interested in message handling. */ private MessageHandler messageHandler; /** * A synchronization lock, used internally. */ private Object synchLock = new Object(); /** * The underlying server socket listened by this server. */ private ServerSocket serverSocket = null; /** * The thread used to run the connections handling routine. */ private Thread thread = null; /** * Established connections. */ private ArrayList connections = new ArrayList(); /** * It builds a ready-to-start lock server. * * @param id * The lock id. * @param messageHandler * The message handler used by this server. It could be null if * who has taken the lock is not interested in message handling. */ public Server(String id, MessageHandler messageHandler) { this.id = id; this.messageHandler = messageHandler; } /** * It starts the server. * * @throws IllegalStateException * If this server is already started. */ public void start() throws IllegalStateException { synchronized (synchLock) { // Tests the server status by checking the serverSocket property // value. if (serverSocket != null) { throw new IllegalStateException("JUnique/Server for id \"" + id + "\" already started"); } // Opens the server socket. try { serverSocket = new ServerSocket(); SocketAddress socketAddress = new InetSocketAddress("localhost", 0); serverSocket.bind(socketAddress); } catch (IOException e) { // Release the server socket. if (serverSocket != null) { try { serverSocket.close(); } catch (Throwable t) { ; } } // Throws a runtime exception. throw new RuntimeException( "Unexpected IOException while starting " + "JUnique/Server for id \"" + id + "\"", e); } // Starts the connections handling routine. thread = new Thread(this, "JUnique/Server/" + id); thread.setDaemon(true); thread.start(); // Waits for start signal. do { try { synchLock.wait(); break; } catch (InterruptedException e) { ; } } while (true); } } /** * It stops the server. */ public void stop() { synchronized (synchLock) { // Tests the server status by checking the serverSocket property // value. if (serverSocket == null) { throw new IllegalStateException("JUnique/Server for id \"" + id + "\" not started"); } // Stops any ongoing communication. while (connections.size() > 0) { Connection c = (Connection) connections.remove(0); c.stop(); } // Issues an interrupt signal to the secondary thread. thread.interrupt(); // Close the serverSocket. try { serverSocket.close(); } catch (IOException e) { ; } // Waiting for server exiting. do { try { thread.join(); break; } catch (InterruptedException e) { ; } } while (true); // Discards references. serverSocket = null; } } /** * Connections handling routine, running on a separate thread. */ public void run() { // Sends start signal. synchronized (synchLock) { synchLock.notify(); } // Multiple connections handled with multi-threading. while (!Thread.interrupted()) { try { Socket incoming = serverSocket.accept(); Connection c = new Connection(id, incoming, this); synchronized (synchLock) { connections.add(c); c.start(); } } catch (Throwable t) { ; } } } /** * It returns the port on which by this server is listening. * * @return The port on which by this server is listening. * @throws IllegalStateException * If the server is not started. */ public int getListenedPort() throws IllegalStateException { synchronized (synchLock) { // Tests the server status by checking the serverSocket property // value. if (serverSocket == null) { throw new IllegalStateException("JUnique/Server for id \"" + id + "\" not started"); } // Returns the value. return serverSocket.getLocalPort(); } } /** * Called to notify a connection close. */ public void connectionClosed(Connection connection) { synchronized (connections) { connections.remove(connection); } } /** * Handle a message reception. */ public String messageReceived(Connection connection, String message) { if (messageHandler != null) { synchronized (messageHandler) { // Thread-safe return messageHandler.handle(message); } } return null; } }