Mercurial > hg > accesspd
view java/src/uk/ac/qmul/eecs/ccmi/network/Server.java @ 0:78b7fc5391a2
first import, outcome of NIME 2014 hackaton
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Tue, 08 Jul 2014 16:28:59 +0100 |
parents | |
children |
line wrap: on
line source
/* CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.qmul.eecs.ccmi.network; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Queue; import java.util.ResourceBundle; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; import uk.ac.qmul.eecs.ccmi.gui.Diagram; import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; /** * The {@code Server} is a thread the user can start, which accept connections from other clients * (other users machines running the CCmI editor). * When a user shares a diagram via the server, other clients * will be able to see and to modify it in real time. This is achieved by exchanging network * message between the server and each client. * On a shared diagram scenario the server * diagram model is considered to be the "real" one. That is, the other clients will continuously * synchronize their model with the server and even before applying a command issued by an action of * the local user, clients need first send the command to the server (which will apply the command on its own model) * and wait for an acknowledging reply. * */ public class Server extends NetworkThread { /** * Create a new server instance. If another instance is already running then it's shut * down before the new instance is created. * * @return a {@code Server} instance. */ public static Server createServer(){ if(server != null) server.shutdown(server.resources.getString("log.restart")); server = new Server(); return server; } public static Server getServer(){ return server; } private Server(){ super("Server Thread"); mustSayGoodbye = false; initialized = false; resources = ResourceBundle.getBundle(this.getClass().getName()); } public void init() throws IOException { if(initialized) return; serverChannel = ServerSocketChannel.open(); selector = Selector.open(); diagrams = new HashMap<String,Diagram>(); scManager = new ServerConnectionManager(diagrams,getAwarenessPanelEditor()); String portAsString = PreferencesService.getInstance().get("server.local_port",Server.DEFAULT_LOCAL_PORT); int port = Integer.parseInt(portAsString); serverChannel.socket().bind(new InetSocketAddress(port)); serverChannel.configureBlocking(false); serverChannel.register(selector, SelectionKey.OP_ACCEPT); Handler serverLogHandler = new Handler(){ @Override public void close() throws SecurityException {} @Override public void flush() {} @Override public void publish(LogRecord record) { NarratorFactory.getInstance().speakWholeText(record.getMessage()); } }; serverLogHandler.setLevel(Level.CONFIG); logger.addHandler(serverLogHandler); logger.config("Server initialized, will listen on port: "+port); initialized = true; } @Override public void run(){ logger.config(resources.getString("log.start")); running = true; while(!mustSayGoodbye){ try{ selector.select(); if(mustSayGoodbye) break; for (Iterator<SelectionKey> itr = selector.selectedKeys().iterator(); itr.hasNext();){ SelectionKey key = itr.next(); itr.remove(); if(!key.isValid()) continue; if(key.isAcceptable()){ SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); /* log connection only if it's not from the local channel */ if(!"/127.0.0.1".equals(clientChannel.socket().getInetAddress().toString())) logger.info(resources.getString("log.client_connected")); } if(key.isReadable()){ try{ scManager.handleMessage((SocketChannel)key.channel()); }catch(IOException ioe){ /* Upon exception the channel is no longer considered reliable: it's cleaned up and closed */ SocketChannel channel = (SocketChannel)key.channel(); channel.close(); scManager.removeChannel(channel); logger.info(ioe.getLocalizedMessage()); } } } }catch(IOException e){ logger.severe(e.getLocalizedMessage()); e.printStackTrace(); break; } } cleanup(); } private void cleanup(){ try { /* close all the channels */ serverChannel.close(); for (Iterator<SelectionKey> itr = selector.keys().iterator(); itr.hasNext();){ SelectionKey key = itr.next(); ((Closeable)key.channel()).close(); } } catch (IOException e) { e.printStackTrace(); } running = false; } public void shutdown(String msg){ if(msg != null) NarratorFactory.getInstance().speak(resources.getString("log.shutdown")+msg); shutdown(); } public void shutdown(){ mustSayGoodbye = true; selector.wakeup(); server = null; for(Handler h : logger.getHandlers()){ h.close(); logger.removeHandler(h); } } public Queue<DiagramElement> getLocalhostQueue(String diagramName){ return scManager.getLocalhostMap().get(diagramName); } /* this is called by the event dispatching thread when the user shares a diagram */ public void share(Diagram diagram) throws ServerNotRunningException, DiagramAlreadySharedException{ String name = diagram.getName(); if(!running) throw new ServerNotRunningException(resources.getString("exception_msg.not_run")); synchronized(diagrams){ if(diagrams.containsKey(name)){ throw new DiagramAlreadySharedException(resources.getString("exception_msg.already_shared")); } diagrams.put(name,diagram); } scManager.getLocalhostMap().put(name, new ConcurrentLinkedQueue<DiagramElement>()); logger.info("Diagram "+name+" shared on the server"); } private static Server server; public static final String DEFAULT_LOCAL_PORT = "7777"; public static final String DEFAULT_REMOTE_PORT = "7777"; static Logger logger; private ServerSocketChannel serverChannel; private ServerConnectionManager scManager; private Selector selector; private Map<String, Diagram> diagrams; private ResourceBundle resources; private boolean initialized; private volatile boolean mustSayGoodbye; private boolean running; static { logger = Logger.getLogger(Server.class.getName()); logger.setUseParentHandlers(false); logger.setLevel(Level.CONFIG); } }