/** * Copyright (c) 2011-2014, OpenIoT * * This file is part of OpenIoT. * * OpenIoT is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * OpenIoT is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with OpenIoT. If not, see <http://www.gnu.org/licenses/>. * * Contact: OpenIoT mailto: info@openiot.eu */ package org.openiot.cupus.entity.subscriber; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.UUID; import org.openiot.cupus.artefact.Publication; import org.openiot.cupus.artefact.Subscription; import org.openiot.cupus.entity.NetworkEntity; import org.openiot.cupus.entity.subscriber.NotificationListener; import org.openiot.cupus.entity.subscriber.SubscriberInterface; import org.openiot.cupus.message.Message; import org.openiot.cupus.message.external.NotifyMessage; import org.openiot.cupus.message.external.SubscribeMessage; import org.openiot.cupus.message.external.SubscriberDisconnectMessage; import org.openiot.cupus.message.external.SubscriberRegisterMessage; import org.openiot.cupus.message.external.SubscriberUnregisterMessage; import org.openiot.cupus.util.LogWriter; import org.openiot.cupus.util.ReadingWritingXML; /** * Primary class for subscriber. It contains most functionality and contains * data needed for subscriber to function. * * @author Aleksandar, Eugen * */ public class Subscriber extends NetworkEntity implements SubscriberInterface { protected List<Publication> publicationList = new ArrayList<Publication>(); //List of all Subscriptions protected List<Subscription> allSubs = new ArrayList<Subscription>(); // List od all active subscriptions protected List<Subscription> activeSubs = new ArrayList<Subscription>(); protected List<Subscription> outboxSubs = new ArrayList<Subscription>(); private NotificationListener notificationListener; private boolean connected = false; private String myBrokerIP; private int myBrokerPort; private LogWriter log; private boolean logWriting = false; private boolean testing = true; /** Socket for sending messages (subscriptions and connect/disconnect/etc.) to the Broker */ private Socket sendingSocket = null; private ObjectOutputStream sendingOut = null; //private ObjectInputStream sendingIn; /** Socket for incoming notifications (about matched publications) from the Broker */ private Socket receivingSocket = null; //private ObjectOutputStream receivingOut; private ObjectInputStream receivingIn = null; private Object publicationListMutex = new Object(); /** * Constructor - subscriber can be created via configuration file or * directly * * @param myName Subscriber's name * @param myBrokerIP Publisher's connecting broker IP address * @param myBrokerPort Publisher's connecting broker port */ public Subscriber(String myName, String myBrokerIP, int myBrokerPort) { super(myName, getLocalIP(), -1); if (this.myIP.equals("")) { log.writeToLog("Does not have correct IP address " + this.myIP, true); this.myIP = "localhost"; } this.myBrokerIP = myBrokerIP; this.myBrokerPort = myBrokerPort; this.connected = false; log = new LogWriter(this.myName + "_subscriberLog.txt", logWriting, testing); log.writeToLog("Subscriber name: " + this.myName, true); log.writeToLog("Subscriber broker port: " + this.myBrokerPort, true); log.writeToLog("Subscriber broker IP: " + this.myBrokerIP, true); log.writeToLog("", true); } /** * Constructor - subscriber can be created via configuration file or * directly */ public Subscriber(File configFile) { super("", getLocalIP(), -1); if (this.myIP.equals("")) { log.writeToLog("Does not have correct IP address " + this.myIP, true); this.myIP = "localhost"; } try { Properties subProps = new Properties(); FileInputStream fileIn = new FileInputStream(configFile); subProps.load(fileIn); fileIn.close(); this.myName = subProps.getProperty("subscriberName"); if (this.myName==null) throw new NullPointerException("Name must be defined!"); this.myBrokerIP = subProps.getProperty("brokerIP"); if (this.myBrokerIP==null) throw new NullPointerException("BrokerIP must be defined!"); this.myBrokerPort = Integer.parseInt(subProps.getProperty("brokerPort")); if (subProps.getProperty("testing", "false").toLowerCase().equals("false")){ this.testing = false; } else if (subProps.getProperty("testing").toLowerCase().equals("true")){ this.testing = true; } else { System.err.println("Config param \"testing\" should be either true or false! Setting to default false."); this.testing = false; } if (subProps.getProperty("logWriting", "true").toLowerCase().equals("true")){ this.logWriting = true; } else if (subProps.getProperty("logWriting").toLowerCase().equals("false")){ this.logWriting = false; } else { System.err.println("Config param \"logWriting\" should be either true or false! Setting to default true."); this.logWriting = true; } } catch (Exception e){ e.printStackTrace(); System.exit(-1); } log = new LogWriter(this.myName + "_subscriberLog.txt", logWriting, testing); log.writeToLog("Subscriber name: " + this.myName, true); log.writeToLog("Subscriber broker port: " + this.myBrokerPort, true); log.writeToLog("Subscriber broker IP: " + this.myBrokerIP, true); log.writeToLog("", true); } /** * Used for connecting subscriber to broker */ @Override public void connect() { if (connected){ log.writeToLog("Connect request received while being connected. Ignored."); return; } try { sendingSocket = new Socket(this.myBrokerIP, this.myBrokerPort); this.myPort = sendingSocket.getLocalPort(); sendingOut = new ObjectOutputStream(sendingSocket.getOutputStream()); sendingOut.flush(); } catch (UnknownHostException ex) { log.writeToLog("Connecting failed - Unknown Broker Host or Port: " + ex); try {sendingSocket.close();} catch (Exception e){} sendingSocket = null; sendingOut = null; return; } catch (IOException ex) { log.writeToLog("Failed to open stream to the Broker: " + ex); try {sendingSocket.close();} catch (Exception e){} sendingSocket = null; sendingOut = null; return; } ServerSocket brokerBackConnectSpot = null; try { brokerBackConnectSpot = new ServerSocket(0); brokerBackConnectSpot.setSoTimeout(10000); } catch (Exception e1){ log.error("Unable to open ServerSocket for broker back-connection!"); try {sendingSocket.close();} catch (Exception e2){} sendingSocket = null; sendingOut = null; return; } int brokerBackConnectPort = brokerBackConnectSpot.getLocalPort(); //send the register message Message connectMessage = new SubscriberRegisterMessage(myName, this.getId(), myIP, brokerBackConnectPort); System.out.println(myIP+ " "+brokerBackConnectPort); this.sendMessage(connectMessage); //wait for 'response' - in the form of connect request from Broker's DeliveryService try { receivingSocket = brokerBackConnectSpot.accept(); receivingIn = new ObjectInputStream(receivingSocket.getInputStream()); } catch (Exception e1){ log.error("Unable to establish back-connection from broker!"); log.error("Exception: "+e1.getMessage()); try {sendingSocket.close();} catch (Exception e2){} sendingSocket = null; sendingOut = null; return; } this.connected = true; log.writeToLog("Connected to Broker "+myBrokerIP+":"+myBrokerPort); //create broker listener that will handle incoming notify messages Thread brokerListener = new Thread(new SubscriberListenerThread()); brokerListener.start(); //processing subscriptions that were added while disconnected... Iterator<Subscription> iterator = outboxSubs.iterator(); while (iterator.hasNext()) { subscribe(iterator.next()); iterator.remove(); } } /** * Used for handling negative response message (reconnecting to broker) * * @param brokerIP IP address of reconnecting broker * @param brokerPort Port number of reconnecting broker */ @Override public void reconnect(String brokerIP, int brokerPort) { if (connected){ log.writeToLog("Disconnecting from broker (part of reconnect)."); disconnectFromBroker(); } this.myBrokerIP = brokerIP; this.myBrokerPort = brokerPort; this.connect(); } /** * Reconnect to last connected broker */ @Override public void reconnect() { if (connected){ log.writeToLog("Disconnecting from broker (part of reconnect)."); disconnectFromBroker(); } this.connect(); } /** * Used for disconnecting from broker */ @Override public void disconnectFromBroker() { if (connected) { Message disconnectMessage = new SubscriberDisconnectMessage(myName, getId()); this.sendMessage(disconnectMessage); //TODO FIXME no confirmation is waited for... it is just assumed the communication went ok terminateConnection(); log.writeToLog("Disconnected from broker!"); } else { log.writeToLog("Cannot disconnect from broker because not connected."); } } /** * Used for unregistering from broker (meaning losing all subscriptions) */ @Override public void unregisterFromBroker() { if (connected) { Message unregisterMessage = new SubscriberUnregisterMessage(myName, getId()); this.sendMessage(unregisterMessage); //TODO FIXME no confirmation is waited for... it is just assumed the communication went ok terminateConnection(); activeSubs.clear(); //clearing them from here to be in sync with situation on broker outboxSubs.clear(); //same as above (because they wre made while still being registered to that broker) log.writeToLog("Unregistered from broker!"); } else { log.writeToLog("Cannot unregister from broker because not connected."); } } /** * For terminating the connection... * closes to outSocket and sets everything to null */ private void terminateConnection(){ try { sendingSocket.close(); //the receiving scket will be closed from the server side } catch (Exception e){ //ignore } sendingSocket = null; sendingOut = null; receivingSocket = null; receivingIn = null; this.connected = false; } /** * Subscribing subscription from XML file * * @param fileName Name of input file * @return returns subscription UUID */ public UUID subscribeFromXMLFile(String fileName) { return subscribe(fileName, ""); } /** * Subscribing subscription from string * * @param inputString string containing XML subscription * @return returns subscription UUID */ public UUID subscribeFromXMLString(String inputString) { return subscribe("", inputString); } /** * Subscribing subscription from XML file or content (not both - one has to * be empty string ("")) * * @param fileName Name of input file * @param inputString Name of input String * @return returns subscription UUID */ public UUID subscribe(String fileName, String inputString) { ReadingWritingXML input = new ReadingWritingXML(fileName, inputString); input.read(); Subscription sub = input.createSubscription(); this.subscribe(sub); return sub.getId(); } /** * Used for subscribing * * @param subscription Subscription to be subscribed */ public void subscribe(Subscription subscription) { if (connected) { Message sendMasg = new SubscribeMessage(subscription, false); this.sendMessage(sendMasg); log.writeToLog("Subscription "+subscription+" sent to broker."); //TODO no confirmation is waited for here... if (!allSubs.contains(subscription)) { allSubs.add(subscription); activeSubs.add(subscription); } else if (!activeSubs.contains(subscription)) { activeSubs.add(subscription); } } else { if (!allSubs.contains(subscription)) { outboxSubs.add(subscription); allSubs.add(subscription); } else if (!outboxSubs.contains(subscription)) { outboxSubs.add(subscription); } log.writeToLog("Subscription "+subscription+" put in outbox because not connected to broker."); } } /** * Used for unsubscribing * * @param subscription Subscription to be unsubscribed */ public void unsubscribe(Subscription subscription) { if (activeSubs.contains(subscription)) { if (connected) { Message sendMsg = new SubscribeMessage(subscription, true); this.sendMessage(sendMsg); log.writeToLog("Unsubscription request sent to broker."); } activeSubs.remove(subscription); } else if (outboxSubs.contains(subscription)){ outboxSubs.remove(subscription); log.writeToLog("Subscription unsubscribed from outbox. No need to contact the broker."); } else { log.writeToLog("Unsubscription impossible because subscription is no longer active."); } } /** * Used for sending messages to broker * * @param sendMsg message to be sent */ protected void sendMessage(Message sendMsg) { try { sendingOut.writeObject(sendMsg); sendingOut.flush(); } catch (Exception e1){ log.error("Message "+sendMsg+" not sent. Disconnecting because of connection problems."); e1.printStackTrace(); terminateConnection(); } } public void setNotificationListener( NotificationListener notificationListener) { this.notificationListener = notificationListener; } public boolean isConnected() { return connected; } public boolean isLogWriting() { return logWriting; } public boolean isTesting() { return testing; } /** * Thread for receiving notify messages from the Broker's * DeliveryService component. */ private class SubscriberListenerThread implements Runnable { @Override public void run() { while (connected) { Object objIn = null; try { objIn = receivingIn.readObject(); } catch (Exception e){ log.error("Error on input stream from Broker. Terminating connection..."); terminateConnection(); log.writeToLog("Disconnected from Broker."); return; } if (objIn instanceof NotifyMessage) { NotifyMessage msg = (NotifyMessage)objIn; notify(msg.getPublication(), msg.isUnpublish()); } else { log.writeToLog("Unkown request/response received from broker (type = "+ objIn.getClass().getName()+"). Ignoring..."); } } } /** * Used for handling notifications about new publications * @param publication New Publication */ public void notify(Publication publication, boolean unpublish) { synchronized (publicationListMutex) { if (!unpublish) publicationList.add(publication); else publicationList.remove(publication); } if (!unpublish){ log.writeToLog("Received a publication from broker ("+publication.getId()+")"); notificationListener.notify(getId(), myName, publication); } else { log.writeToLog("Received an unpublication from broker ("+publication.getId()+")"); } } } }