annotate java/src/uk/ac/qmul/eecs/ccmi/network/Server.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
rev   line source
f@0 1 /*
f@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0 3
f@0 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0 5
f@0 6 This program is free software: you can redistribute it and/or modify
f@0 7 it under the terms of the GNU General Public License as published by
f@0 8 the Free Software Foundation, either version 3 of the License, or
f@0 9 (at your option) any later version.
f@0 10
f@0 11 This program is distributed in the hope that it will be useful,
f@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 14 GNU General Public License for more details.
f@0 15
f@0 16 You should have received a copy of the GNU General Public License
f@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 18 */
f@0 19
f@0 20 package uk.ac.qmul.eecs.ccmi.network;
f@0 21
f@0 22 import java.io.Closeable;
f@0 23 import java.io.IOException;
f@0 24 import java.net.InetSocketAddress;
f@0 25 import java.nio.channels.SelectionKey;
f@0 26 import java.nio.channels.Selector;
f@0 27 import java.nio.channels.ServerSocketChannel;
f@0 28 import java.nio.channels.SocketChannel;
f@0 29 import java.util.HashMap;
f@0 30 import java.util.Iterator;
f@0 31 import java.util.Map;
f@0 32 import java.util.Queue;
f@0 33 import java.util.ResourceBundle;
f@0 34 import java.util.concurrent.ConcurrentLinkedQueue;
f@0 35 import java.util.logging.Handler;
f@0 36 import java.util.logging.Level;
f@0 37 import java.util.logging.LogRecord;
f@0 38 import java.util.logging.Logger;
f@0 39
f@0 40 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
f@0 41 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
f@0 42 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
f@0 43 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
f@0 44
f@0 45 /**
f@0 46 * The {@code Server} is a thread the user can start, which accept connections from other clients
f@0 47 * (other users machines running the CCmI editor).
f@0 48 * When a user shares a diagram via the server, other clients
f@0 49 * will be able to see and to modify it in real time. This is achieved by exchanging network
f@0 50 * message between the server and each client.
f@0 51 * On a shared diagram scenario the server
f@0 52 * diagram model is considered to be the "real" one. That is, the other clients will continuously
f@0 53 * synchronize their model with the server and even before applying a command issued by an action of
f@0 54 * the local user, clients need first send the command to the server (which will apply the command on its own model)
f@0 55 * and wait for an acknowledging reply.
f@0 56 *
f@0 57 */
f@0 58 public class Server extends NetworkThread {
f@0 59
f@0 60 /**
f@0 61 * Create a new server instance. If another instance is already running then it's shut
f@0 62 * down before the new instance is created.
f@0 63 *
f@0 64 * @return a {@code Server} instance.
f@0 65 */
f@0 66 public static Server createServer(){
f@0 67 if(server != null)
f@0 68 server.shutdown(server.resources.getString("log.restart"));
f@0 69 server = new Server();
f@0 70 return server;
f@0 71 }
f@0 72
f@0 73 public static Server getServer(){
f@0 74 return server;
f@0 75 }
f@0 76
f@0 77 private Server(){
f@0 78 super("Server Thread");
f@0 79 mustSayGoodbye = false;
f@0 80 initialized = false;
f@0 81 resources = ResourceBundle.getBundle(this.getClass().getName());
f@0 82 }
f@0 83
f@0 84 public void init() throws IOException {
f@0 85 if(initialized)
f@0 86 return;
f@0 87 serverChannel = ServerSocketChannel.open();
f@0 88 selector = Selector.open();
f@0 89 diagrams = new HashMap<String,Diagram>();
f@0 90 scManager = new ServerConnectionManager(diagrams,getAwarenessPanelEditor());
f@0 91 String portAsString = PreferencesService.getInstance().get("server.local_port",Server.DEFAULT_LOCAL_PORT);
f@0 92 int port = Integer.parseInt(portAsString);
f@0 93 serverChannel.socket().bind(new InetSocketAddress(port));
f@0 94 serverChannel.configureBlocking(false);
f@0 95 serverChannel.register(selector, SelectionKey.OP_ACCEPT);
f@0 96 Handler serverLogHandler = new Handler(){
f@0 97 @Override
f@0 98 public void close() throws SecurityException {}
f@0 99
f@0 100 @Override
f@0 101 public void flush() {}
f@0 102
f@0 103 @Override
f@0 104 public void publish(LogRecord record) {
f@0 105 NarratorFactory.getInstance().speakWholeText(record.getMessage());
f@0 106 }
f@0 107 };
f@0 108 serverLogHandler.setLevel(Level.CONFIG);
f@0 109 logger.addHandler(serverLogHandler);
f@0 110 logger.config("Server initialized, will listen on port: "+port);
f@0 111 initialized = true;
f@0 112 }
f@0 113
f@0 114 @Override
f@0 115 public void run(){
f@0 116 logger.config(resources.getString("log.start"));
f@0 117 running = true;
f@0 118 while(!mustSayGoodbye){
f@0 119 try{
f@0 120 selector.select();
f@0 121 if(mustSayGoodbye)
f@0 122 break;
f@0 123 for (Iterator<SelectionKey> itr = selector.selectedKeys().iterator(); itr.hasNext();){
f@0 124 SelectionKey key = itr.next();
f@0 125 itr.remove();
f@0 126
f@0 127 if(!key.isValid())
f@0 128 continue;
f@0 129 if(key.isAcceptable()){
f@0 130 SocketChannel clientChannel = serverChannel.accept();
f@0 131 clientChannel.configureBlocking(false);
f@0 132 clientChannel.register(selector, SelectionKey.OP_READ);
f@0 133 /* log connection only if it's not from the local channel */
f@0 134 if(!"/127.0.0.1".equals(clientChannel.socket().getInetAddress().toString()))
f@0 135 logger.info(resources.getString("log.client_connected"));
f@0 136 }
f@0 137 if(key.isReadable()){
f@0 138 try{
f@0 139 scManager.handleMessage((SocketChannel)key.channel());
f@0 140 }catch(IOException ioe){
f@0 141 /* Upon exception the channel is no longer considered reliable: it's cleaned up and closed */
f@0 142 SocketChannel channel = (SocketChannel)key.channel();
f@0 143 channel.close();
f@0 144 scManager.removeChannel(channel);
f@0 145 logger.info(ioe.getLocalizedMessage());
f@0 146 }
f@0 147 }
f@0 148 }
f@0 149 }catch(IOException e){
f@0 150 logger.severe(e.getLocalizedMessage());
f@0 151 e.printStackTrace();
f@0 152 break;
f@0 153 }
f@0 154 }
f@0 155 cleanup();
f@0 156 }
f@0 157
f@0 158 private void cleanup(){
f@0 159 try {
f@0 160 /* close all the channels */
f@0 161 serverChannel.close();
f@0 162 for (Iterator<SelectionKey> itr = selector.keys().iterator(); itr.hasNext();){
f@0 163 SelectionKey key = itr.next();
f@0 164 ((Closeable)key.channel()).close();
f@0 165 }
f@0 166 } catch (IOException e) {
f@0 167 e.printStackTrace();
f@0 168 }
f@0 169 running = false;
f@0 170 }
f@0 171
f@0 172 public void shutdown(String msg){
f@0 173 if(msg != null)
f@0 174 NarratorFactory.getInstance().speak(resources.getString("log.shutdown")+msg);
f@0 175 shutdown();
f@0 176 }
f@0 177
f@0 178 public void shutdown(){
f@0 179 mustSayGoodbye = true;
f@0 180 selector.wakeup();
f@0 181 server = null;
f@0 182 for(Handler h : logger.getHandlers()){
f@0 183 h.close();
f@0 184 logger.removeHandler(h);
f@0 185 }
f@0 186 }
f@0 187
f@0 188 public Queue<DiagramElement> getLocalhostQueue(String diagramName){
f@0 189 return scManager.getLocalhostMap().get(diagramName);
f@0 190 }
f@0 191
f@0 192 /* this is called by the event dispatching thread when the user shares a diagram */
f@0 193 public void share(Diagram diagram) throws ServerNotRunningException, DiagramAlreadySharedException{
f@0 194 String name = diagram.getName();
f@0 195 if(!running)
f@0 196 throw new ServerNotRunningException(resources.getString("exception_msg.not_run"));
f@0 197 synchronized(diagrams){
f@0 198 if(diagrams.containsKey(name)){
f@0 199 throw new DiagramAlreadySharedException(resources.getString("exception_msg.already_shared"));
f@0 200 }
f@0 201 diagrams.put(name,diagram);
f@0 202 }
f@0 203 scManager.getLocalhostMap().put(name, new ConcurrentLinkedQueue<DiagramElement>());
f@0 204 logger.info("Diagram "+name+" shared on the server");
f@0 205 }
f@0 206
f@0 207 private static Server server;
f@0 208 public static final String DEFAULT_LOCAL_PORT = "7777";
f@0 209 public static final String DEFAULT_REMOTE_PORT = "7777";
f@0 210 static Logger logger;
f@0 211 private ServerSocketChannel serverChannel;
f@0 212 private ServerConnectionManager scManager;
f@0 213 private Selector selector;
f@0 214 private Map<String, Diagram> diagrams;
f@0 215 private ResourceBundle resources;
f@0 216 private boolean initialized;
f@0 217 private volatile boolean mustSayGoodbye;
f@0 218 private boolean running;
f@0 219
f@0 220 static {
f@0 221 logger = Logger.getLogger(Server.class.getName());
f@0 222 logger.setUseParentHandlers(false);
f@0 223 logger.setLevel(Level.CONFIG);
f@0 224 }
f@0 225 }