/** * */ package AP2DX; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.ConnectException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.DelayQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.LogRecord; import java.util.logging.Logger; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import AP2DX.specializedMessages.HelloMessage; /** * Baseclass for AP2DX components<br> * <br> * Can read config file<br> * Can write logfile<br> * Can listen at socket<br> * Can send to socket[s]<br> * * @author Jasper Timmer * @author Wadie Assal * */ public abstract class AP2DXBase { /** Log object */ public static Logger logger; /** The configuration read from file and set by readConfig */ protected Map config; /** The read message queue logic that calls the concrete logic */ private Logic baseLogic; /** The delay of the last message to add to the current message, given in milliseconds */ private long lastDelay; /** Threadcounter for outgoing connections */ private AtomicInteger threadCounter = new AtomicInteger(); /** This is module... */ protected final Module IAM; /** list containing all outgoing connections that are active */ private ArrayList<ConnectionHandler> connections = new ArrayList<ConnectionHandler>(); /** all received AP2DX.Messages will be stored here */ private DelayQueue<AP2DXMessage> receiveQueue = new DelayQueue<AP2DXMessage>(); /** All the incoming bidirectional connections */ private ArrayList<Module> passiveConnections = new ArrayList<Module>(); /** * Where everything happens: * <ul> * <li>Read the config file</li> * <li>Init the logger</li> * <li>Start the logic thread</li> * <li>Read the config file</li> * <li>Establish incoming connections</li> * <li>Start outgoing connections</li> * <li>And if defined, execute extra logic</li> * </ul> * * @author Jasper Timmer * */ public AP2DXBase(Module myModule) { IAM = myModule; System.out.println("Starting AP2DXBase"); config = readConfig(); initLogger(); logger.info("Package: " + this.getClass().getPackage().getName()); logger.info("Directory: " + System.getProperty("user.dir")); baseLogic = new Logic(this); baseLogic.start(); // Establish all the connections. // Incoming connections ServerSocket svr = null; try { svr = new ServerSocket(Integer.parseInt(config.get("listen_port").toString())); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); //System.exit(1); } System.out.println("After ServerSocket"); // start new thread that accepts all connections and creates connectionhandlers Listener conn_c = new Listener(svr, this); Thread t = new Thread(conn_c); t.start(); // Outgoing connections // Get the list of outgoing connections from the configfile List out = (List) config.get("outgoing"); // for every connection, start a connector thread for (int i = 0; i < out.size(); i++) { int port = Integer.parseInt(((JSONObject) out.get(i)).get("port") .toString()); String address = ((JSONObject) out.get(i)).get("address") .toString(); Module module = Module.valueOf(((JSONObject) out.get(i)).get( "component").toString()); boolean bidirectional = Boolean.parseBoolean(((JSONObject) out.get(i)).get("bidirectional").toString()); boolean passive = Boolean.parseBoolean(((JSONObject) out.get(i)).get("passive").toString()); if (!passive) { Connector connector = new Connector(this, address, port, module, bidirectional); Thread t1 = new Thread(connector); try { t1.start(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); //System.exit(1); } threadCounter.incrementAndGet(); } else { passiveConnections.add(module); } } // wait for all the connectors to connect while (this.threadCounter.get() != 0) { try { Thread.sleep(100); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Start connectionchecker to remove dead connections // and restart outgoing connections ConnectionChecker CC = new ConnectionChecker(this); Thread t2 = new Thread(CC); try { t2.start(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); //System.exit(1); } System.out.println("Before override"); // run the extra logic this.doOverride(); try { baseLogic.join(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("End of construc tor!"); } /** * If needed, extra logic for the constructor of the concrete class can be added here. * This is nessecary because the component constructor calls super() but will * never return. It waits for the BaseLogic to return. */ protected void doOverride() { // override this for extra behaviour in concrete class. } /** * Starts a logger and defines the log format. Gets the logfile location * from the configfile variable "logfile". */ private void initLogger() { try { boolean append = true; FileHandler fh = new FileHandler(config.get("logfile").toString(), append); // set logfile format: // date millis methodname level message fh.setFormatter(new Formatter() { public String format(LogRecord rec) { StringBuffer buf = new StringBuffer(1000); buf.append(new java.util.Date()); buf.append(' '); buf.append(rec.getMillis()); buf.append(' '); buf.append(rec.getSourceMethodName()); buf.append(' '); buf.append(rec.getLevel()); buf.append(' '); buf.append(formatMessage(rec)); buf.append('\n'); return buf.toString(); } }); logger = Logger.getLogger(this.getClass().getPackage().getName()); logger.addHandler(fh); } catch (IOException e) { e.printStackTrace(); } } /** * read configfile. The config file is the package name with ".json" * appended. * * @return jsonMap A raw key/value map with the values from the jsonfile. */ protected Map readConfig() { String jsonText = getContents(new File( this.getClass() .getPackage() .getName() + ".json" )); Object jsonObj = null; Map jsonMap = null; JSONParser parser = new JSONParser(); try { jsonObj = parser.parse(jsonText); jsonMap = (Map) jsonObj; } catch (ParseException pe) { System.out.println("position: " + pe.getPosition()); System.out.println(pe); } return jsonMap; } /** * Fetch the entire contents of a text file, and return it in a String. This * style of implementation does not throw Exceptions to the caller. * * @param aFile * is a file which already exists and can be read. */ static protected String getContents(File aFile) { // ...checks on aFile are elided StringBuilder contents = new StringBuilder(); try { // use buffering, reading one line at a time // FileReader always assumes default encoding is OK! BufferedReader input = new BufferedReader(new FileReader(aFile)); try { String line = null; // not declared within while loop /* * readLine is a bit quirky : it returns the content of a line * MINUS the newline. it returns null only for the END of the * stream. it returns an empty String if two newlines appear in * a row. */ while ((line = input.readLine()) != null) { contents.append(line); contents.append(System.getProperty("line.separator")); } } finally { input.close(); } } catch (IOException ex) { ex.printStackTrace(); } return contents.toString(); } /** * Compares old connection's module with new connection's module. Replaces * connection object for the same module. * * @author Wadie Assal * @param connection * @return bool update succeeded */ public boolean UpdateConnection(ConnectionHandler connection) { ConnectionHandler conn; for (int i = 0; i < connections.size(); i++) { conn = connections.get(i); if (conn.moduleID.compareTo(connection.moduleID) == 0) { connections.set(i, connection); return true; } } return false; } /** * return the ConnectionHandler for the requested module. * * @author Wadie Assal * @param module The module to get connection for * @return conn The requested connection object or null */ public ConnectionHandler getConnection(Module module) throws Exception { ConnectionHandler conn; for (int i = 0; i < connections.size(); i++) { conn = connections.get(i); if (conn.moduleID.compareTo(module) == 0) { return conn; } } return null; } /** * Establishes a new connection and adds it to the outConnections list * * @param ipaddress * @param port * @param destination Module it connects to * @return true if operation succeeded false otherwise */ private boolean addConnection(String ipaddress, int port, Module destination, boolean bidirection) { try { Socket sock = new Socket(ipaddress, port); // not a usar sim connection, so first value is false ConnectionHandler conn = new ConnectionHandler(false, bidirection, this, sock, IAM, destination); connections.add(conn); return true; } catch (Exception e) { return false; } } public ArrayList<Module> getPassiveConnections() { return passiveConnections; } public ArrayList<AP2DXMessage> componentLogicCheck(Message msg) { //System.out.println("In componentLogicCheck"); if (!msg.getMsgType().isAp2dxMessage && !IAM.canAcceptJsonMessages) { //System.err.println("Unexpected message in " + IAM + ". Received a non-AP2DX message type and cannot accept JSON messages."); return null; } return componentLogic(msg); } /** * List with received messages * @param msg * @return */ public abstract ArrayList<AP2DXMessage> componentLogic(Message msg); /** * * @return delayqueue with all waiting messages */ public DelayQueue<AP2DXMessage> getReceiveQueue() { return receiveQueue; } /** * Sets the delay for the next message so it can't pass the last one. * * @param millisec new delay in milliseconds */ public void setLastDelay(long millisec) { this.lastDelay = System.nanoTime() + TimeUnit.NANOSECONDS.convert(millisec, TimeUnit.MILLISECONDS); } /** * * @return The time until last delay has expired in milliseconds */ public long getLastDelay() { long n = lastDelay - System.nanoTime(); return TimeUnit.MILLISECONDS.convert(n, TimeUnit.NANOSECONDS); } /** * Class to create outgoing connection * * @author Jasper Timmer * */ private class Connector implements Runnable { /** Ipaddress of the connection */ private String address; /** Port of the connection */ private int port; /** The module to connect to */ private Module module; /** Reference to the baseclass */ private AP2DXBase base; /** Should this connection be treated as incoming connection */ private boolean bidirectional; /** * Constructor * Only sets variables **/ public Connector(AP2DXBase base, String address, int port, Module module, boolean bidirectional) { this.address = address; this.port = port; this.module = module; this.base = base; this.bidirectional = bidirectional; } /** * the body of the thread, * tries until forever to connect to the given computer */ @Override public void run() { Socket conn = null; boolean success = false; while (!success) { try { conn = new Socket(this.address, this.port); success = true; } catch (ConnectException e2) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // keep trying! } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); //System.exit(1); } } ConnectionHandler connHandler = null; try { connHandler = new ConnectionHandler(false, bidirectional, base, conn, IAM, this.module); if (bidirectional) { connHandler.start(); } HelloMessage message = new HelloMessage(IAM, this.module, bidirectional); connHandler.sendMessage(message); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } // add the connectionhandler to the list of connections connections.add(connHandler); // decrement the runningthread counter this.base.threadCounter.decrementAndGet(); } } /** * This class is used to check the lists of incoming and outgoing connections, * for dead links. If nessecary, try to reestablish connections. * * Error: can't change the list inConnections and outConnections from other thread * while looping over it. * * @author Jasper Timmer * */ private class ConnectionChecker implements Runnable { private AP2DXBase base; public ConnectionChecker(AP2DXBase base) { this.base = base; } @Override public void run() { while (true) { ArrayList<ConnectionHandler> connections = (ArrayList<ConnectionHandler>) base.connections.clone(); // check if outgoing connection has closed and attempt to reconnect for (ConnectionHandler conn : connections) { if (!conn.isConnAlive()) { if (conn.isBidirectional() && !getPassiveConnections().contains(conn.moduleID)) { int port = conn.getPort(); String address = conn.getAddress(); boolean bidirectional = conn.isBidirectional(); Module module = conn.getModule(); Connector connector = new Connector(this.base, address, port, module, bidirectional); Thread t1 = new Thread(connector); try { base.threadCounter.incrementAndGet(); t1.start(); } catch (Exception e) { e.printStackTrace(); } } base.connections.remove(conn); } } try { Thread.sleep(100); } catch (InterruptedException e) { break; } } } } /** * Class for listening at specified port and accepting connections. * * @author Jasper Timmer * */ private class Listener implements Runnable { /** Socket to listen on */ private ServerSocket server; /** Reference to the base */ private AP2DXBase base; /** * Sets variables * @param svr * @param base */ public Listener(ServerSocket svr, AP2DXBase base) { this.server = svr; this.base = base; } /** * Listen and accept connections forever. <br/> * <br/> * Timeout on new sockets is 250 millis */ @Override public void run() { while (true) { System.out.println("Started Listener"); ConnectionHandler connHandler = null; Socket conn = null; try { System.out.println("Waiting for connection"); conn = this.server.accept(); System.out.println("Connection starting"); // important: timeout on all connections is set here conn.setSoTimeout(250); AP2DXBase.logger.info(String.format("New connection with %s", conn.getRemoteSocketAddress())); connHandler = new ConnectionHandler(base, conn, IAM); connHandler.start(); System.out.printf("Adding connection %s to connections\n", connHandler.moduleID); base.connections.add(connHandler); } catch (Exception e) { // something wend terribly wrong, terminate module. AP2DXBase.logger.severe(String.format("Error in connectionhandler: %s\n%s", conn.getRemoteSocketAddress(), e.getMessage())); e.printStackTrace(); //System.exit(1); } } } } }