package tmcsim.paramicscommunicator;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import tmcsim.common.CADProtocol.PARAMICS_ACTIONS;
import tmcsim.common.CADProtocol.PARAMICS_COMM_TAGS;
import tmcsim.paramicscommunicator.FileIOUpdate.IO_TYPE;
import tmcsim.paramicscommunicator.FileRegUpdate.REG_TYPE;
import tmcsim.paramicscommunicator.gui.ParamicsCommunicatorGUI;
/**
* ParamicsCommunicator is the main class for this module. The Paramics
* Communicator is used to provide communication between the CAD Simulator and
* the Paramics traffic modeler. While the application is running, data is
* received on a socket from the CAD Simulator. Transmitted data are XML
* documents containing information and action commands. The CAD Simulator
* registers readers and writers with the ParamicsCommunicator. Any data read by
* a ParamicsReader is sent back to the CAD Simulator. All data to be written by
* a ParamicsWriter is received through the socket.
* The properties file for the ParamicsCommunicator class contains the following
* data.
*
* -----------------------------------------------------------------------------
* Socket Port The port number to use for socket communication.
* Working Directory The working directory use for Paramics file
* communication.
* Error File The target file to use for error logging.
* -----------------------------------------------------------------------------
* Example File:
* SocketPort = 4450
* WorkingDirectory = c:\\tmc_simulator\\
* ErrorFile = sim_mgr_error.xml
* -----------------------------------------------------------------------------
*
*
* @author Matthew Cechini (mcechini@calpoly.edu)
* @version $Date: 2009/04/17 16:27:46 $ $Revision: 1.7
*/
public class ParamicsCommunicator extends Observable implements Observer
{
/**
* Error logger.
*/
private static Logger paramLogger = Logger.getLogger("tmcsim.paramicscommunicator");
/**
* Enumeration containing property names.
*
* @author Matthew Cechini
*/
private static enum PROPERTIES
{
SOCKET_PORT("SocketPort"),
WORKING_DIR("WorkingDirectory");
public String name;
private PROPERTIES(String n)
{
name = n;
}
}
/**
* Properties object.
*/
private Properties paramicsCommProp = null;
/**
* Current working directory where files will be read and written
*/
private String workingDirectory = null;
/**
* Socket used to create socket communication with the CAD Simulator.
*/
private ServerSocket serverSocket = null;
/**
* Soccket used to communicate with CAD Simulator.
*/
private Socket paramicsSocket = null;
/**
* Input Stream for reading data from the CAD Simulator.
*/
private ObjectInputStream in = null;
/**
* Output Stream for writing data to the CAD Simulator.
*/
private ObjectOutputStream out = null;
/**
* Map of all current ParamicsFileWriters referenced by I/O ID.
*/
private TreeMap writers = null;
/**
* Map of all current ParamicsFileReaders referenced by I/O ID.
*/
private TreeMap readers = null;
/**
* The view class for the ParamicsCommunicator.
*/
private static ParamicsCommunicatorGUI theGUI;
/**
* The thread used to initialize the socket connection with the CADSimulator
*/
Thread initThread;
/**
* Constructor. Read in the property values. If the properties file does not
* contain a value for the working directory, open a dialog to prompt the
* user for the path of the Paramics working directory. An empty string is
* not accepted. A null signifies that the user pressed cancel. Prompt the
* user to accept the cancel and exit the application if confirmed. Continue
* until a valid directory has been entered, that exists, and append a '\'
* to the end of the directory if necessary.
*
* Initialize the Sockets and begin communication.
*
* @param propertiesFilePath File Path of ParamicsCommunicator properties
* file.
*/
public ParamicsCommunicator(String propertiesFile)
{
writers = new TreeMap();
readers = new TreeMap();
try
{
paramicsCommProp = new Properties();
paramicsCommProp.load(new FileInputStream(propertiesFile));
if (paramicsCommProp.getProperty(PROPERTIES.SOCKET_PORT.name) == null)
{
JOptionPane.showMessageDialog(theGUI,
"Properties file missing CAD Simulator Port information.",
"Invalid Configuration", JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
else if (paramicsCommProp.getProperty(PROPERTIES.WORKING_DIR.name) == null
|| paramicsCommProp.getProperty(PROPERTIES.WORKING_DIR.name).length() == 0)
{
try
{
String workingDir = null;
while (workingDir == null || workingDir.length() == 0)
{
workingDir = JOptionPane.showInputDialog(null,
"Please set the output directory for Paramics communication.",
"Paramics Working Directory", JOptionPane.QUESTION_MESSAGE);
if (workingDir == null)
{
}
else if (!new File(workingDir).exists())
{
JOptionPane.showMessageDialog(null,
"Directory does not exist.",
"Invalid Working Directory", JOptionPane.WARNING_MESSAGE);
workingDir = null;
}
else if (!new File(workingDir).isDirectory())
{
JOptionPane.showMessageDialog(null,
workingDir + " is not a directory.",
"Invalid Working Directory", JOptionPane.WARNING_MESSAGE);
workingDir = null;
}
}
if (workingDir.lastIndexOf("\\") != workingDir.length() - 1)
{
workingDir = workingDir + "\\";
}
paramicsCommProp.setProperty(PROPERTIES.WORKING_DIR.name, workingDir);
paramicsCommProp.store(new FileOutputStream(propertiesFile), "");
} catch (IOException ioe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Constructor",
"Exception in writing properties file.", ioe);
}
}
workingDirectory = paramicsCommProp.getProperty(
PROPERTIES.WORKING_DIR.name).trim();
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Constructor",
"Exception in reading properties file.", e);
}
Integer desiredPort = Integer.parseInt(paramicsCommProp.getProperty(
PROPERTIES.SOCKET_PORT.name).trim());
/* Start a thread to initialize the sockets that talk to CADsimulator */
initThread = new Thread(new SocketStarter(desiredPort));
initThread.start();
}
/**
* For testing, we want to be able to provide a non-visible instance of the
* GUI to be used. (As main won't be called).
*
* @param viewer
*/
public void setGUI(ParamicsCommunicatorGUI viewer)
{
theGUI = viewer;
addObserver(theGUI);
}
/**
* Start the thread that does the main work of the communicator. This method
* waits until the initial thread has opened the needed sockets to connect
* to the CADSimulator, then it starts the socket reader thread, which runs
* until the application is terminated. Usage: Normally this method is
* called immediately after constructing this object, but for testing
* purposes it is available to be invoked by the test harness after the
* Simulation Manager initiates a connection.
*/
public void startReading()
{
while (initThread.isAlive())
{
try
{
Thread.sleep(1000);
} catch (InterruptedException ex)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "runstarter",
"Sleep interrupted.");
}
}
Runnable sr = new SocketReader();
new Thread(sr).start();
}
/**
* Transmits a message XML document object to the CAD Simulator.
*
* @param mess The ParamicsCommMessage to be transmitted.
*/
private void write(Document mess)
{
synchronized (paramicsSocket)
{
try
{
out.writeObject(mess);
out.flush();
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "write",
"Exception in writing to the socket.", e);
}
}
}
/**
* Observer/Observable update method. The Paramics Communicator observers
* registered ParamicsReaders. When messages are to be sent, they are sent
* through this method. All messages are ParamicsCommMessage objects. Send
* these messages to the write() method for transmission on the socket.
*/
public void update(Observable o, Object arg)
{
if (arg instanceof Document)
{
write((Document) arg);
}
}
private class SocketReader implements Runnable
{
/**
* Runnable method. While this thread is not interrupted, read in an
* object from the socket input stream. If an object exists, call
* doMessage() to parse and perform the received action in the message.
*/
public void run()
{
while (true)
{
try
{
doMessage((Document) in.readObject());
} catch (SocketTimeoutException ste)
{
//just try again
} catch (EOFException eofe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"run", "EOF Exception in reading data from the socket.", eofe);
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"run", "Exception in reading data from the socket.", e);
JOptionPane.showMessageDialog(theGUI,
"Connection has been lost to the CAD Simulator. "
+ "Paramics Communicator will now shutdown.",
"Dropped Connection", JOptionPane.ERROR_MESSAGE);
break;
}
try
{
// Sleep for a second while waiting in this infinite loop
// to yield to other threads run by test harness.
Thread.sleep(1000);
} catch (InterruptedException ex)
{
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"run", "Exception in reading data from the socket.", ex);
}
}
try
{
in.close();
} catch (Exception e)
{
}
try
{
out.close();
} catch (Exception e)
{
}
try
{
serverSocket.close();
} catch (Exception e)
{
}
try
{
paramicsSocket.close();
} catch (Exception e)
{
}
}
}
/**
* Perform the action represented in the received XML document message.
* First determine if the action is from a READER, WRITER, and RESET. If the
* paramics action is REGISTER, add a new ParamicsFileReader/Writer to the
* local list of readers/writers and update the GUI with a FileRegUpdate
* object. If the paramics action is UNREGISTER, remove the
* ParamicsFileReader/Writer from the local list of readers/writers and
* update the GUI with a FileRegUpdate object. If RESET is received, clear
* all readers and writers.
*
* @param mess Received XML document message.
*/
private void doMessage(Document mess)
{
Element rootElement = mess.getDocumentElement();
String id = null;
String action = null;
switch (PARAMICS_COMM_TAGS.fromString(rootElement.getNodeName()))
{
case READER:
id = rootElement.getAttribute(PARAMICS_COMM_TAGS.ID.tag);
action = rootElement.getAttribute(PARAMICS_COMM_TAGS.ACTION.tag);
switch (PARAMICS_ACTIONS.fromString(action))
{
case REGISTER:
Integer interval = Integer.parseInt(rootElement.getChildNodes().item(0).getTextContent());
String targetFile = rootElement.getChildNodes().item(1).getTextContent();
readers.put(id, new ParamicsFileReader(workingDirectory, id,
interval, targetFile));
readers.get(id).addObserver(this);
readers.get(id).addObserver(theGUI);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.READ,
REG_TYPE.REGISTER, id, targetFile, interval));
break;
case UNREGISTER:
readers.get(id).deleteObserver(this);
readers.get(id).deleteObserver(theGUI);
readers.remove(id);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.READ,
REG_TYPE.UNREGISTER, id, null, null));
break;
}
break;
case WRITER:
id = rootElement.getAttribute(PARAMICS_COMM_TAGS.ID.tag);
action = rootElement.getAttribute(PARAMICS_COMM_TAGS.ACTION.tag);
switch (PARAMICS_ACTIONS.fromString(action))
{
case REGISTER:
String targetFile = rootElement.getChildNodes().item(0).getTextContent();
writers.put(id, new ParamicsFileWriter(id,
workingDirectory, targetFile));
writers.get(id).addObserver(theGUI);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.WRITE,
REG_TYPE.REGISTER, id, targetFile, null));
break;
case UNREGISTER:
writers.remove(id);
writers.get(id).deleteObserver(theGUI);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.WRITE,
REG_TYPE.UNREGISTER, id, null, null));
break;
case WRITE_FILE:
writers.get(id).writeMessage((Element) rootElement.getChildNodes().item(0));
break;
}
break;
case RESET:
readers.clear();
writers.clear();
break;
}
}
private class SocketStarter implements Runnable
{
Integer socketPort;
/**
* Method waits to accept a socket connection from the CAD Simulator.
* When a connection has been established the method exits. The input
* and output streams are created on the new socket.
*
* @param socketPort Socket port to use for establishing Socket
* communication.
* @throws IOException if there is an exception in establishing Socket
* communication.
*/
//private void initializeSockets(Integer socketPort) throws IOException
public SocketStarter(Integer socketPort)
{
this.socketPort = socketPort;
}
public void run()
{
boolean waiting = true;
try
{
serverSocket = new ServerSocket(socketPort);
//delay for accept timeout(milliseconds)
serverSocket.setSoTimeout(10 * 1000);
} catch (IOException ioe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"initializeSockets", "Exception in creating "
+ "the server socket on port " + socketPort + ", terminating.");
System.exit(1);
}
while (waiting)
{
try
{
paramicsSocket = serverSocket.accept();
waiting = false;
} catch (SocketTimeoutException ste)
{
System.out.println("...waiting...");
try
{
Thread.sleep(2000);
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"initializeSockets", "sleeping.");
} catch (InterruptedException ex)
{
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"initializeSockets", "Exception exiting for socket.", ex);
}
} catch (IOException ioe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"initializeSockets", "Exception in creating "
+ "the receiving socket on port " + socketPort + ", terminating.");
System.exit(1);
}
}
//** out must be performed before in to unlock for connecting socket **//
try
{
out = new ObjectOutputStream(paramicsSocket.getOutputStream());
in = new ObjectInputStream(paramicsSocket.getInputStream());
} catch (IOException ioe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"initializeSockets", "Exception in creating "
+ "input and output streams on socket, terminating.");
System.exit(1);
}
}
}
/**
* Construct the ParamicsCommunicator with the properties file path, either
* from the command line arguments or default.
*
* @param args Command line arguments.
*/
public static void main(String[] args)
{
System.setProperty("PARAMICS_COMM_PROPERTIES", "config/paramics_communicator_config.properties");
try
{
if (System.getProperty("PARAMICS_COMM_PROPERTIES") != null)
{
ParamicsCommunicator pc = new ParamicsCommunicator(System.getProperty("PARAMICS_COMM_PROPERTIES"));
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
theGUI = new ParamicsCommunicatorGUI();
pc.addObserver(theGUI);
theGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
theGUI.setVisible(true);
pc.startReading();
}
else
{
throw new Exception("PARAMICS_COMM_PROPERTIES system property not defined.");
}
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Main",
"Error occured initializing application", e);
JOptionPane.showMessageDialog(null, e.getMessage(),
"Error - Program Exiting", JOptionPane.ERROR_MESSAGE);
System.exit(-1);
}
}
}