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 }
|