source: tmcsimulator/trunk/src/tmcsim/cadsimulator/CADClientConnector.java @ 660

Revision 660, 17.3 KB checked in by jdalbey, 4 years ago (diff)

!CADClientConnector.java Catch socketexception to drop clients that have disconnected. Hope this fixes #160.

Line 
1package tmcsim.cadsimulator;
2
3import java.io.IOException;
4import java.io.ObjectInputStream;
5import java.io.ObjectOutputStream;
6import java.net.Socket;
7import java.net.SocketException;
8import java.util.Observable;
9import java.util.Observer;
10import java.util.logging.Level;
11import java.util.logging.Logger;
12
13import javax.xml.parsers.DocumentBuilderFactory;
14
15import org.w3c.dom.Document;
16import org.w3c.dom.Element;
17import org.w3c.dom.Node;
18
19import tmcsim.common.CADProtocol;
20import tmcsim.common.ObserverMessage;
21import tmcsim.common.CADEnums.CAD_KEYS;
22import tmcsim.common.CADProtocol.CAD_CLIENT_CMD;
23import tmcsim.common.CADProtocol.CAD_COMMANDS;
24import tmcsim.common.CADProtocol.CAD_SIMULATOR_CMD;
25
26
27/**
28 * CADClientConnector handles communication between the CAD Simulator and
29 * remote CAD Clients.  Each instance of this class communicates with
30 * a CAD Client through a socket.  The run() method continuously checks to see
31 * if data has been received from the client.  If there is data, it is parsed,
32 * and the resulting action is performed by the CADScreenManager.  See the
33 * receiveObject() method description for more information.  The
34 * CADSimulatorClient is set up as an Observer of the CADScreenManager to listen
35 * for ObserverMessage objects.  For each object received, the appropriate
36 * action is taken, resulting in data being transmitted to the CAD Client.
37 * See the update() method description for more information.  The
38 * CADScreenManager is set up as an observer of the Coordinator to listen
39 * for simulation data updates.
40 *
41 * @author Matthew Cechini (mcechini@calpoly.edu)
42 * @version $Date: 2006/06/14 00:12:38 $ $Revision: 1.5 $
43 */
44public class CADClientConnector extends Thread implements Observer {
45   
46    /** Error Logger. */
47    private static Logger cadLogger = Logger.getLogger("tmcsim.cadsimulator");
48   
49    /** CADScreenManager object containing the data for managing the CAD Client's view information. */
50    private CADScreenManager screenManager;
51   
52    /** Socket used for communication with the CAD Client. */
53    private Socket theSocket;
54   
55    /** ObjectOutputStream for writing objects to the socket. */
56    private ObjectOutputStream out;
57   
58    /** ObjectInputStream for reading objects from the socket. */
59    private ObjectInputStream in;
60   
61    /**
62     * Constructor.  A CADScreenManager is instantiated to manage the output
63     * transmitted to the remote CAD Client.  This object is set up as an
64     * observer to that manager to listen for data that will be transmitted
65     * across the socket.  The CADScreenManager is set up as an observer
66     * of the Coordinator.  At construction, streams are created to handle
67     * reading and writing to the Socket.  When complete, the sendScreenRefresh()
68     * method is called to initialize the client.
69     *
70     *
71     * @param newSocket The socket to use for data transmission
72     * @throws IOException if there is an error in getting the output or input streams
73     * from the socket.
74     */
75    CADClientConnector(Socket newSocket) throws IOException{
76       
77        screenManager = new CADScreenManager(CADServer.theCoordinator);     
78        CADServer.theCoordinator.addObserver(screenManager);
79        screenManager.addObserver(this);
80       
81       
82        theSocket = newSocket;       
83        out       = new ObjectOutputStream(theSocket.getOutputStream());
84        in        = new ObjectInputStream(theSocket.getInputStream());   
85   
86        //initialize the CAD client
87        sendScreenRefresh();
88    }
89   
90    /**
91     * Method declaration for the Thread.run() method.  While the thread is not
92     * interrupted, read Objects from the socket and call the receiveObject()
93     * method to parse the data.  If there is an IOException in communicating
94     * with the client, interrupt this thread and close the streams and Socket.
95     */
96    public void run() {
97       
98        try { 
99       
100            while(!isInterrupted()) {
101                receiveObject(in.readObject());         
102            }
103        } 
104        catch (ClassCastException cce) {
105            cce.printStackTrace();
106        }
107        catch (ClassNotFoundException cnfe) {
108            cnfe.printStackTrace();
109        }
110        catch (SocketException except)
111        {
112            if (except.getMessage().endsWith("reset"))
113            {
114                cadLogger.logp(Level.INFO, "CADClientConnector", "run", "Connection reset, removing client.\n");
115                disconnectClient();
116            }
117        }
118        catch (IOException ioe) {                   
119            cadLogger.logp(Level.SEVERE, "CADSimulatorClient", "run", 
120                    "Error in reading from Client socket: " + 
121                    theSocket.getInetAddress() + ", proceeding.\n", ioe);
122           
123            //disconnectClient();
124        }
125    }
126   
127    /**
128     * This method is called to disconnect from the remote CAD Client.
129     * This object's thread is interrupted and the streams and socket
130     * are closed.  This object is removed as an observer of the
131     * CADScreenManager and the CADScreenManager is removed as an
132     * observer of the Coordinator.  The viewer is then notified
133     * of a disconnecting client.
134     */
135    protected void disconnectClient() {
136        this.interrupt();
137       
138        try { out.close(); } catch (Exception e) {}
139        try { in.close(); } catch (Exception e) {}
140        try { theSocket.close(); } catch (Exception e) {}
141
142        screenManager.deleteObserver(this);
143        CADServer.theCoordinator.removeObserver(screenManager);
144        //CADSimulator.theViewer.disconnectClient();
145    }
146   
147   
148
149    /**
150     * Observer method.  The update argument is cast to an ObserverMessage
151     * object and the message type is used to define the action taken by the
152     * CADSimulatorClient.  Command messages are created in the form of XML
153     * Document. The root Element name is value from the CAD_SIMULATOR_CMD
154     * enumeration.  The root text content is the value Object from the
155     * received Observer argument.
156     *
157     * The following table describes the messages sent for the ObserverMessage
158     * types.<br>
159     *
160     *<table cellpadding="2" cellspacing="2" border="1"
161    * style="text-align: left; width: 250px;">
162    *  <tbody>
163    *    <tr>
164    *      <th>Observer Message Type<br></th>
165    *      <th>Command Message Type<br></th>
166    *      <th>Data Content<br></th>
167    *    </tr>
168    *    <tr>
169    *      <td>SCREEN_UPDATE<br></td>
170    *      <td>UPDATE_STATUS</td>
171    *      <td>Screen update map String.</td>
172    *    </tr>
173    *    <tr>
174    *      <td>TIME_UPDATE<br></td>
175    *      <td>UPDATE_TIME</td>
176    *      <td>CAD time String.  (HHMM)</td>
177    *    </tr>
178    *    <tr>
179    *      <td>ROUTED_MESSAGE<br></td>
180    *      <td>UPDATE_MSG_COUNT</td>
181    *      <td>Number of messages.</td>
182    *    </tr>
183    *    <tr>
184    *      <td><br></td>
185    *      <td>UPDATE_MSG_UNREAD</td>
186    *      <td>Boolean flag to designate unread messages.</td>
187    *    </tr>
188    *    <tr>
189    *      <td>CAD_INFO_MESSAGE<br></td>
190    *      <td>CAD_INFO</td>
191    *      <td></td>
192    *    </tr>
193    *    <tr>
194    *      <td>REFRESH_VIEW<br></td>
195    *      <td>UPDATE_SCREEN</td>
196    *      <td>Current CAD model XML data.</td>
197    *    </tr>   
198    *  </tbody>
199    *</table>
200     *
201     * @see ObserverMessage
202     * @see CAD_SIMULATOR_CMD
203     */ 
204    public void update(Observable o, Object arg) {
205       
206        ObserverMessage oMessage = (ObserverMessage)arg;
207        CAD_SIMULATOR_CMD simCmd = null;
208       
209        switch(oMessage.type) {
210            case SCREEN_UPDATE:
211                simCmd = CAD_SIMULATOR_CMD.UPDATE_STATUS;
212                break;
213                       
214            case TIME_UPDATE:
215                simCmd = CAD_SIMULATOR_CMD.UPDATE_TIME;
216                break;
217               
218            case ROUTED_MESSAGE:
219                sendRoutedMessageUpdate();
220                break;
221               
222            case CAD_INFO_MESSAGE:
223                simCmd = CAD_SIMULATOR_CMD.CAD_INFO;
224                break;     
225               
226            case REFRESH_VIEW:
227                sendScreenRefresh();
228                break;
229        }
230       
231
232        if(simCmd != null) {
233            try {
234               
235                Document cmdDoc = DocumentBuilderFactory.newInstance()
236                        .newDocumentBuilder().newDocument();
237                cmdDoc.appendChild(cmdDoc.createElement(
238                        simCmd.type));
239                cmdDoc.getDocumentElement().appendChild(
240                        cmdDoc.createTextNode(oMessage.value.toString()));
241                transmitCommand(cmdDoc);
242   
243            } catch (Exception e) {
244                cadLogger.logp(Level.SEVERE, "CADSimulatorClient", "update", 
245                        "Error in transmitting a command to client.", e);
246            }           
247        }
248       
249    }
250   
251    /**
252     * This method acts as a helper method to create a XML Document
253     * update messages with the number of routed messages and
254     * whether there are unread messages for this client.  These
255     * two messages are sent separately due to the defined
256     * command protocol.
257     */
258    private void sendRoutedMessageUpdate() {
259
260        try {           
261            Document cmdDoc = DocumentBuilderFactory.newInstance()
262                .newDocumentBuilder().newDocument();
263            Element docElem = cmdDoc.createElement(
264                    CAD_SIMULATOR_CMD.UPDATE_MSG_COUNT.type);
265            docElem.appendChild(cmdDoc
266                    .createTextNode(String.valueOf(screenManager
267                            .getCurrentCADModel().numberRoutedMessages)));
268            cmdDoc.appendChild(docElem);
269            transmitCommand(cmdDoc);
270           
271
272            cmdDoc = DocumentBuilderFactory.newInstance()
273                .newDocumentBuilder().newDocument();
274            docElem = cmdDoc.createElement(
275                    CAD_SIMULATOR_CMD.UPDATE_MSG_UNREAD.type);
276            docElem.appendChild(cmdDoc
277                    .createTextNode(String.valueOf(screenManager
278                            .getCurrentCADModel().unreadMessages)));
279            cmdDoc.appendChild(docElem);
280            transmitCommand(cmdDoc);           
281
282        } catch (Exception e) {
283            cadLogger.logp(Level.SEVERE, "CADSimulatorClient", 
284                    "sendRoutedMessageUpdate", 
285                    "Error in transmitting a command to client.", e);
286        }   
287    }
288   
289    /**
290     * This method acts as a helper method to create an XML Document
291     * update message with the current CAD Model's XML information.
292     */
293    private void sendScreenRefresh() {
294       
295        try {           
296            Document cmdDoc = DocumentBuilderFactory.newInstance()
297                    .newDocumentBuilder().newDocument();
298            Element docElem = cmdDoc.createElement(
299                    CAD_SIMULATOR_CMD.UPDATE_SCREEN.type);
300           
301            screenManager.getCurrentCADModel().toXML(docElem);
302           
303            cmdDoc.appendChild(docElem);
304            transmitCommand(cmdDoc);
305
306        } catch (Exception e) {
307            cadLogger.logp(Level.SEVERE, "CADSimulatorClient", 
308                    "sendScreenRefresh", 
309                    "Error in transmitting a command to client.", e);
310        }               
311    }
312   
313    /**
314     * This method parses the data that has been received on the socket.  The
315     * Data is cast to an XML Document and the root element determines the
316     * data content.  The possible root elements and the corresponding action
317     * are explained below. <br>
318     * <code>
319     * -----------<br>
320     * TERMINAL_REGISTER<br>
321     *
322     * The CAD position and user ID are parsed from the Element and these
323     * values are sent to the CADScreenManager for use.<br>
324     * -----------<br>
325     * SAVE_COMMAND_LINE<br>
326     *
327     * The current command line text is parsed from the Element and sent
328     * to the CADScreenManager for user.
329     * <br>
330     * -----------<br>
331     * TERMINAL_CMD_LINE<br>
332     * The CAD command is parsed from the Element and converted to a
333     * CAD_CLIENT_CMD enumeration that is used to call the correct
334     * method in the CADScreenManager to perform the command.
335     * <br>
336     * -----------<br>
337     * TERMINAL_FUNCTION<br>
338     *
339     * The key value is parsed from the Element and converted to a CAD_KEYS
340     * enumeration that is sent to the CADScreenManager to perform the
341     * associated action.
342     * <br>
343     * -----------<br>
344     * TERMINATE<br>
345     *
346     * <br>
347     * -----------<br>
348     * </code>
349     * @param receivedData String of data received on the socket.
350     *
351     * @see CADProtocol
352     */
353    private void receiveObject(Object rxData) throws IOException {
354       
355        try {   
356           
357            Element root  = ((Document)rxData).getDocumentElement();
358       
359            switch(CAD_CLIENT_CMD.fromString(root.getNodeName())) {
360           
361                case TERMINAL_REGISTER:
362                    Node positionNode = root.getChildNodes().item(0);
363                    screenManager.setCADPosition(Integer.parseInt(positionNode.getTextContent()));
364                   
365                    Node userIDNode   = root.getChildNodes().item(1);
366                    screenManager.setCADUserID(userIDNode.getTextContent());                           
367                    break;         
368                 
369                case SAVE_COMMAND_LINE:
370                    screenManager.receiveCommandLine(root.getTextContent());
371                    break;
372                case TERMINAL_CMD_LINE:
373                   
374                    Node commandNode = root.getChildNodes().item(0);
375
376                    switch(CAD_COMMANDS.fromFullName(commandNode.getNodeName())) {
377                        case INCIDENT_BOARD:
378                            screenManager.incidentBoardRequest((Element)commandNode);
379                            break;
380                        case INCIDENT_UPDATE:       
381                            screenManager.incidentUpdateRequest((Element)commandNode);
382                            break;
383                        case INCIDENT_INQUIRY:                 
384                            screenManager.incidentInquiryRequest((Element)commandNode);
385                            break;
386                        case INCIDENT_SUMMARY:             
387                            screenManager.incidentSummaryRequest((Element)commandNode);
388                            break;
389                        case ROUTED_MESSAGE:           
390                            screenManager.routedMessageRequest((Element)commandNode);
391                            break;
392                        case ENTER_INCIDENT:           
393                            screenManager.enterIncidentRequest((Element)commandNode);
394                            break;
395                        case TERMINAL_OFF:             
396                            screenManager.terminalOffRequest();
397                            break;
398                        case APP_CLOSE:             
399                           
400                            try {
401                                Document cmdDoc = DocumentBuilderFactory.newInstance()
402                                        .newDocumentBuilder().newDocument();
403                                cmdDoc.appendChild(cmdDoc.createElement(CAD_SIMULATOR_CMD.
404                                        APP_CLOSE.type));                               transmitCommand(cmdDoc);
405                   
406                            } catch (Exception e) {
407                                cadLogger.logp(Level.SEVERE, "CADSimulatorClient", "update", 
408                                        "Error in transmitting a command to client.", e);
409                            }   
410
411                            //disconnectClient();
412                            break;
413                        case UNKNOWN:
414                            //TODO
415                            break;
416                    }
417                    break;
418           
419                case TERMINAL_FUNCTION:             
420                    screenManager.receiveCommand(CAD_KEYS.fromValue(
421                            root.getTextContent().substring(
422                                    0, root.getTextContent().indexOf(":")),                         
423                            new Integer(root.getTextContent().substring(
424                                    root.getTextContent().indexOf(":") + 1))));
425                    break;
426            }     
427        }
428        catch (ClassCastException cce) {
429            cadLogger.logp(Level.SEVERE, "CADSimulatorClient", 
430                    "receiveObject", 
431                    "Incorrect object received from client.", cce);
432        }
433    }   
434   
435    /**
436     * This method transmits the Document command message to the remote
437     * CAD Client.  If an exception occurs in writing to the socket, an
438     * Exception is thrown and socket communication is closed.
439     *
440     * @param data The data being transmitted
441     * @throws IOException if there is an exception in writing to the socket.
442     */
443    private void transmitCommand(Document data) throws IOException {
444 
445        try {
446            out.writeObject(data);
447            out.flush();
448        } catch (IOException ioe) {
449            cadLogger.logp(Level.SEVERE, "CADSimulatorClient", 
450                    "transmitCommand",  "Error writing to Client socket: " + 
451                    theSocket.getInetAddress() + ", continuing.\n", ioe);
452
453            //disconnectClient();
454        }
455           
456    }
457} 
Note: See TracBrowser for help on using the repository browser.