/** * 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.mobilebroker; import static org.openiot.cupus.common.UniqueObject.getLocalIP; 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.Announcement; import org.openiot.cupus.artefact.Publication; import org.openiot.cupus.artefact.Subscription; import org.openiot.cupus.entity.NetworkEntity; import org.openiot.cupus.entity.mobilebroker.MobileBrokerInterface; import org.openiot.cupus.entity.subscriber.NotificationListener; import org.openiot.cupus.message.Message; import org.openiot.cupus.message.external.AnnounceMessage; import org.openiot.cupus.message.external.MobileBrokerDisconnectMessage; import org.openiot.cupus.message.external.MobileBrokerRegisterMessage; import org.openiot.cupus.message.external.NotifyMessage; import org.openiot.cupus.message.external.NotifySubscriptionMessage; import org.openiot.cupus.message.external.PublishMessage; import org.openiot.cupus.message.external.SubscribeMessage; import org.openiot.cupus.util.LogWriter; import org.openiot.cupus.util.ReadingWritingXML; /** * * @author Aleksandar */ public class MobileBroker extends NetworkEntity implements MobileBrokerInterface { //PUBLICATIONS AND SUBSCRIPTIONS OF A USER //List of all published Publications private ArrayList<Publication> allPubs = new ArrayList<Publication>(); // List od all active Publications private ArrayList<Publication> activePubs = new ArrayList<Publication>(); private ArrayList<Publication> outboxPubs = new ArrayList<Publication>(); 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; protected List<Publication> publicationList = new ArrayList<Publication>(); //INCOMMING SUBSCRIPTIONS // List od all active Publications private ArrayList<Announcement> allAnnouncements = new ArrayList<Announcement>(); private ArrayList<Announcement> activeAnnouncements = new ArrayList<Announcement>(); private ArrayList<Announcement> outboxAnnouncements = new ArrayList<Announcement>(); // List od all active subscriptions from the broker protected List<Subscription> brokerSubs = new ArrayList<Subscription>(); private String myBrokerIP; private int myBrokerPort; private boolean connected; private LogWriter log; private boolean logWriting = true; private boolean testing = true; /** * Socket for sending messages (subscriptions and connect/disconnect/etc.) * to the Broker */ private Socket sendingSocket = null; private ObjectOutputStream sendingOut = null; /** * Socket for incoming notifications (about matched publications) from the * Broker */ private Socket receivingSocket = null; private ObjectInputStream receivingIn = null; private Object publicationListMutex = new Object(); private Object subscriptionListMutex = new Object(); /** * Constructor - mobile broker can be created via configuration file or * directly * * @param myName Mobile Broker's name * @param myBrokerIP Mobile Broker's connecting broker IP address * @param myBrokerPort Mobile Broker's connecting broker port */ public MobileBroker(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 + "_mobilebrokerLog.txt", logWriting, testing); log.writeToLog("Mobile Broker name: " + this.myName, true); log.writeToLog("Mobile Broker broker port: " + this.myBrokerPort, true); log.writeToLog("Mobile Broker broker IP: " + this.myBrokerIP, true); log.writeToLog("", true); } /** * Constructor - subscriber can be created via configuration file or * directly */ public MobileBroker(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 mbProps = new Properties(); FileInputStream fileIn = new FileInputStream(configFile); mbProps.load(fileIn); fileIn.close(); this.myName = mbProps.getProperty("mobilebrokerName"); if (this.myName == null) { throw new NullPointerException("Name must be defined!"); } this.myBrokerIP = mbProps.getProperty("brokerIP"); if (this.myBrokerIP == null) { throw new NullPointerException("BrokerIP must be defined!"); } this.myBrokerPort = Integer.parseInt(mbProps.getProperty("brokerPort")); if (mbProps.getProperty("testing", "false").toLowerCase().equals("false")) { this.testing = false; } else if (mbProps.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 (mbProps.getProperty("logWriting", "true").toLowerCase().equals("true")) { this.logWriting = true; } else if (mbProps.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 + "_mobilebrokerLog.txt", logWriting, testing); log.writeToLog("Mobile Broker name: " + this.myName, true); log.writeToLog("Mobile Broker broker port: " + this.myBrokerPort, true); log.writeToLog("Mobile Broker broker IP: " + this.myBrokerIP, true); log.writeToLog("", true); } /** * Used for connecting mobile broker 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 MobileBrokerRegisterMessage(myName, this.getId(), 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 or subscription messages Thread brokerListener = new Thread(new MobileBroker.MobileBrokerListenerThread()); brokerListener.start(); //processing subscriptions, publications and announcements that were added while disconnected... Iterator<Subscription> iteratorSub = outboxSubs.iterator(); while (iteratorSub.hasNext()) { subscribe(iteratorSub.next()); iteratorSub.remove(); } Iterator<Publication> iteratorPublication = outboxPubs.iterator(); while (iteratorPublication.hasNext()) { publish(iteratorPublication.next()); iteratorPublication.remove(); } Iterator<Announcement> iteratorAnn = outboxAnnouncements.iterator(); while (iteratorAnn.hasNext()) { announce(iteratorAnn.next()); iteratorAnn.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 MobileBrokerDisconnectMessage(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."); } } /** * 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(); } } /** * Publishing publication from XML file * * @param fileName Name of input file * @return returns publication UUID */ public UUID publishFromXMLFile(String fileName) { return publish(fileName, ""); } /** * Publishing publication from string * * @param inputString string containing XML publication * @return returns publication UUID */ public UUID publishFromXMLString(String inputString) { return publish("", inputString); } /** * Publishing publication 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 publication UUID */ public UUID publish(String fileName, String inputString) { ReadingWritingXML input = new ReadingWritingXML(fileName, inputString); if (fileName.equalsIgnoreCase("")) { input.readString(); } else { input.readFile(); } Publication pub = input.createPublication(); this.publish(pub); return pub.getId(); } /** * Used for publishing new publication * * @param publication Publication to be published */ public void publish(Publication publication) { synchronized (subscriptionListMutex) { for (Subscription subscritpion : brokerSubs) { if (!subscritpion.isValid()) { brokerSubs.remove(subscritpion); continue; } if (subscritpion.coversPublication(publication)) { if (connected) { Message sendMsg = new PublishMessage(publication, false); this.sendMessage(sendMsg); log.writeToLog("Publication " + publication + " sent to broker."); //TODO no confirmation is waited for here... activePubs.add(publication); allPubs.add(publication); } else { outboxPubs.add(publication); allPubs.add(publication); log.writeToLog("Publication " + publication + " put in outbox because not connected to broker."); } return; } } } } /** * Used for publishing new publication without checking whether is anyone * interested in publication * * @param publication Publication to be published */ public void forcePublish(Publication publication) { if (connected) { Message sendMsg = new PublishMessage(publication, false); this.sendMessage(sendMsg); log.writeToLog("Publication " + publication + " sent to broker."); //TODO no confirmation is waited for here... activePubs.add(publication); allPubs.add(publication); } else { outboxPubs.add(publication); allPubs.add(publication); log.writeToLog("Publication " + publication + " put in outbox because not connected to broker."); } } /** * Used for unpublishing old publication * * @param publication Publication to be unpublished */ public void unpublish(Publication publication) { if (activePubs.contains(publication)) { if (connected) { Message sendMsg = new PublishMessage(publication, true); this.sendMessage(sendMsg); log.writeToLog("Unpublication request sent to broker."); } activePubs.remove(publication); } else if (outboxPubs.contains(publication)) { outboxPubs.remove(publication); log.writeToLog("Publication unpublished from outbox. No need to contact the broker."); } else { log.writeToLog("Unpublication impossible because publication is not active."); } } /** * Announcing the data source from XML file * * @param fileName Name of input file * @return returns announcement UUID */ public UUID announceFromXMLFile(String fileName) { return announce(fileName, ""); } /** * Announcing the data source from string * * @param inputString string containing XML announcement * @return returns announcement UUID */ public UUID announceFromXMLString(String inputString) { return announce("", inputString); } /** * Announcing the data soruce 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 announcement UUID */ public UUID announce(String fileName, String inputString) { ReadingWritingXML input = new ReadingWritingXML(fileName, inputString); input.read(); Announcement ann = input.createAnnouncement(); this.announce(ann); return ann.getId(); } /** * Used for announcing * * @param announcement Announcement to be published */ public void announce(Announcement announcement) { if (connected) { Message sendMasg = new AnnounceMessage(announcement, false); this.sendMessage(sendMasg); log.writeToLog("Announcement " + announcement + " sent to broker."); //TODO no confirmation is waited for here... if (!allAnnouncements.contains(announcement)) { allAnnouncements.add(announcement); activeAnnouncements.add(announcement); } else if (!activeAnnouncements.contains(announcement)) { activeAnnouncements.add(announcement); } } else { if (!allAnnouncements.contains(announcement)) { allAnnouncements.add(announcement); activeAnnouncements.add(announcement); } else if (!activeAnnouncements.contains(announcement)) { activeAnnouncements.add(announcement); } log.writeToLog("Announcement " + announcement + " put in outbox because not connected to broker."); } } /** * Used for revoke of previously sent announcement * * @param announcement Announcement to be published */ public void revokeAnnouncement(Announcement announcement) { if (activeAnnouncements.contains(announcement)) { if (connected) { Message sendMsg = new AnnounceMessage(announcement, true); this.sendMessage(sendMsg); log.writeToLog("Request for revoke of announcement sent to broker."); } activeAnnouncements.remove(announcement); } else if (outboxAnnouncements.contains(announcement)) { outboxAnnouncements.remove(announcement); log.writeToLog("Announcement removed from outbox. No need to contact the broker."); } else { log.writeToLog("Revoke of announcement impossible because announcement is no longer active."); } } public void setNotificationListener( NotificationListener notificationListener) { this.notificationListener = notificationListener; } public boolean isConnected() { return connected; } public boolean isLogWriting() { return logWriting; } public boolean isTesting() { return testing; } @Override public void unregisterFromBroker() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } /** * Thread for receiving notify messages from the Broker's DeliveryService * component. */ private class MobileBrokerListenerThread 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 if (objIn instanceof NotifySubscriptionMessage) { NotifySubscriptionMessage msg = (NotifySubscriptionMessage) objIn; announcement(msg.getSubscription(), msg.isRevoke()); } 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() + ")"); } } /** * Used for handling new subscriptions from the broker * * @param publication New Publication */ public void announcement(Subscription subscription, boolean unsubscribe) { synchronized (subscriptionListMutex) { if (!unsubscribe) { brokerSubs.add(subscription); notificationListener.notify(getId(), myName, subscription); log.writeToLog("Received subscription from broker (" + subscription.getId() + ")"); } else { brokerSubs.remove(subscription); log.writeToLog("Received unsubscription from broker (" + subscription.getId() + ")"); } } } } }