/**
* 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.broker;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import org.openiot.cupus.artefact.ActiveAnnouncement;
import org.openiot.cupus.artefact.ActivePublication;
import org.openiot.cupus.artefact.ActiveSubscription;
import org.openiot.cupus.artefact.Announcement;
import org.openiot.cupus.artefact.HashtablePublication;
import org.openiot.cupus.artefact.Publication;
import org.openiot.cupus.artefact.Subscription;
import org.openiot.cupus.artefact.TripletAnnouncement;
import org.openiot.cupus.artefact.TripletSubscription;
import org.openiot.cupus.common.SubscriptionDataStructure;
import org.openiot.cupus.message.InternalMessage;
import org.openiot.cupus.message.Message;
import org.openiot.cupus.message.external.AnnounceMessage;
import org.openiot.cupus.message.external.PublishMessage;
import org.openiot.cupus.message.external.SubscribeMessage;
import org.openiot.cupus.message.external.SubscriberUnregisterMessage;
import org.openiot.cupus.message.internal.ErrorMessage;
import org.openiot.cupus.message.internal.InfoMessage;
import org.openiot.cupus.subForest.ActiveSubscriptionForest;
import org.openiot.cupus.util.LogWriter;
/**
* Matcher is a component of a cloud-broker whose job is to keep and
* maintain a part of the BE-Tree structure, perform operations on it
* and forward the operation requests forther down the tree of Matchers.
* It is connected with internal object streams with it's parent and it's
* children Matchers (if it has any) which are seemingly just nodes in the
* BE-Tree structure.
*
* @author Eugen Rozic, Aleksandar Antonic
*
*/
public class Matcher {
private int matcherID;
protected BrokerComm intercomm = null;
private UDPMatchingResultsManager toDeliveryService = null;
protected HashMap<UUID, Long> activeSubscribers = new HashMap<UUID, Long>();
//protected Cnode treeRoot;
protected SubscriptionDataStructure treeRoot;
private boolean testing = false;
private boolean logWriting = false;
protected LogWriter log = null;
/**
* Constructs the matcher and starts it's internal communication
* thread.
*/
protected Matcher(int matcherID, int deliveryServiceInternalUDPPort,
boolean testing, boolean logWriting,
ObjectInputStream in, ObjectOutputStream out) {
this.matcherID = matcherID;
this.toDeliveryService = new UDPMatchingResultsManager(deliveryServiceInternalUDPPort);
this.testing = testing;
this.logWriting = logWriting;
intercomm = new BrokerComm(in, out);
this.treeRoot = new ActiveSubscriptionForest();
this.log = new LogWriter("Matcher_"+matcherID+".log", logWriting, false);
new Thread(intercomm).start();
}
/**
* Kills the (sub)process (which will automatically close the output streams
* to the parent and children processes which will in turn cause their
* input streams to throw EOFException which will cause them to call
* their shutdown.
*/
public void shutdown(){
System.exit(-1);
}
/**
* Returns null if publication not instance of HashtablePublication, otherwise
* a set of subscribers that should be notified about this publication (can be
* an empty set).
*/
public Set<UUID> findMatchingSubscribers(PublishMessage msg) {
Publication pub = ((ActivePublication)msg.getPublication()).getPublication();
if (!(pub instanceof HashtablePublication)){
return null;
}
Set<UUID> matched = treeRoot.findMatchingSubscribers((HashtablePublication)pub);
return matched;
}
public Set<Subscription> findMatchingSubscriptions(ActiveAnnouncement actAnn) {
Announcement ann = actAnn.getAnnouncement();
if (!(ann instanceof TripletAnnouncement)){
return null;
}
Set<Subscription> matched = treeRoot.findMatchingSubscriptions((TripletAnnouncement)ann);
return matched;
}
public int addSubscription(ActiveSubscription subscription) {
if (!(subscription.getSubscription() instanceof TripletSubscription)){
informBroker("Forest (adding) can only work with instances of TripletSubscription, not "+subscription.getSubscription().getClass()+".", true);
}
int retval = treeRoot.addSubscription(subscription);
if (retval==SubscriptionDataStructure.SUB_ADDED){
Long value = activeSubscribers.get(subscription.getSubscriberID());
if (value==null){
activeSubscribers.put(subscription.getSubscriberID(), 1L);
} else {
activeSubscribers.put(subscription.getSubscriberID(), value+1);
}
}
return retval;
}
public int removeSubscription(ActiveSubscription subscription) {
if (!(subscription.getSubscription() instanceof TripletSubscription)){
informBroker("Forest (remove) can only work with instances of TripletSubscription, not "+subscription.getSubscription().getClass()+".", true);
}
int retval = treeRoot.removeSubscription(subscription);
if (retval==SubscriptionDataStructure.SUB_REMOVED){
Long value = activeSubscribers.get(subscription.getSubscriberID());
if (value==1){
activeSubscribers.remove(subscription.getSubscriberID());
} else {
activeSubscribers.put(subscription.getSubscriberID(), value-1);
}
}
return retval;
}
public void deleteSubscriber(SubscriberUnregisterMessage msg) {
treeRoot.deleteSubscriber(msg.getEntityID());
activeSubscribers.remove(msg.getEntityID());
}
/**
* Convinience method that sends an ErrorMessage or an InfoMessage
* to the CloudBroker, depending if the reporting flag is set or not.
* It also logs the message to this Matcher's log file (if logging is on).
*/
public void informBroker(String msg, boolean error){
if (error){
log.writeToLog("ERROR: "+msg);
if (testing)
sendInternalMessage(new ErrorMessage(msg));
} else {
log.writeToLog(msg);
if (testing)
sendInternalMessage(new InfoMessage(msg));
}
}
/**
* Convinience method to send a message to the CloudBroker.
* It has to be synchronized because multiple threads may want to send
* something at the same time.
*/
protected void sendInternalMessage(Object msg){
try {
intercomm.out.writeObject(msg);
intercomm.out.flush();
} catch (IOException e){
//if component found terminated shut everything down...
this.shutdown();
}
}
public int getUDPPort(){
return toDeliveryService.deliveryServiceInternalUDPPort;
}
public boolean isTesting(){
return testing;
}
public boolean isLogWriting() {
return logWriting;
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* For communicating with the starting process (parent/CloudBroker)
*/
protected class BrokerComm implements Runnable {
ObjectInputStream in = null;
ObjectOutputStream out = null;
/**
* Sets the stream and reads from in and sets the singleton classes.
*
*/
public BrokerComm(ObjectInputStream in, ObjectOutputStream out) {
this.in = in;
this.out = out;
}
@Override
public void run() {
while (true){
Object objIn = null;
try {
objIn = in.readObject();
} catch (Exception e){
Matcher.this.shutdown();
}
if (objIn instanceof InternalMessage){
informBroker("Matcher: Unexpected internal message received from parent - "+objIn.getClass().getName(), true);
} else if (objIn instanceof Message){
if (objIn instanceof SubscriberUnregisterMessage){
deleteSubscriber((SubscriberUnregisterMessage)objIn);
} else if (objIn instanceof SubscribeMessage){
SubscribeMessage msg = (SubscribeMessage)objIn;
ActiveSubscription actSub = (ActiveSubscription)msg.getSubscription();
if (msg.isUnsubscribe()){
int retval = removeSubscription(actSub);
switch(retval){
case SubscriptionDataStructure.SUB_REMOVED:
informBroker("Subscription successfully removed!", false);
break;
case SubscriptionDataStructure.SUB_NOT_REMOVED:
break;
}
} else {
int retval = addSubscription(actSub);
switch(retval){
case SubscriptionDataStructure.SUB_ADDED:
informBroker("Subscription successfully added!" + actSub.getSubscription().toString(), false);
break;
case SubscriptionDataStructure.SUB_NOT_ADDED:
informBroker("Subscription not added for unknown reason!", false);
break;
}
}
} else if (objIn instanceof PublishMessage){
PublishMessage msg = (PublishMessage)objIn;
Set<UUID> matched = findMatchingSubscribers(msg);
if (matched!=null && !matched.isEmpty())
toDeliveryService.send(msg, matched);
} else if (objIn instanceof AnnounceMessage){
AnnounceMessage msg = (AnnounceMessage)objIn;
ActiveAnnouncement actAnn = (ActiveAnnouncement)msg.getAnnouncement();
Set<Subscription> matched = findMatchingSubscriptions(actAnn);
if (matched!=null && !matched.isEmpty())
toDeliveryService.send(msg, matched, actAnn.getMobileBrokerID());
} else {
informBroker("Matcher: Unexpected external message received from parent - "+objIn.getClass().getName(), true);
}
} else {
String errMsg = "Matcher: Received message is not of class Message" +
" or InternalMessage! ("+objIn.getClass()+" instead). Ignoring...";
informBroker(errMsg, true);
}
}
}
}
/**
* For establishing, managing and using the UDP connection with the
* DeliveryService that is used for notifing the DeliveryService about
* the results of matching a publication on a Matcher.
*/
private class UDPMatchingResultsManager {
private int deliveryServiceInternalUDPPort = -1;
private DatagramSocket socket = null;
private ByteArrayOutputStream baseOut = null;
public UDPMatchingResultsManager(int port) {
this.deliveryServiceInternalUDPPort = port;
try {
socket = new DatagramSocket(); //bound to any local port
baseOut = new ByteArrayOutputStream(64*1000); //64KB, max for IP packet
} catch (Exception e){
sendInternalMessage(new ErrorMessage("Matcher couldn't start because it couldn't open a UDP socket."));
Matcher.this.shutdown();
}
}
/**
* Sends a UDP packet to the DeliveryService containing the
* PublishMessage and the set of subscriber IDs.
*/
synchronized public void send(PublishMessage msg, Set<UUID> subIDs){
try {
baseOut.reset();
ObjectOutputStream oos = new ObjectOutputStream(baseOut); //has to write a new header each time
oos.writeObject(msg);
oos.writeObject(subIDs);
oos.flush();
//IP address of DeliveryService has to be Loopback (same maschine)
DatagramPacket packet = new DatagramPacket(baseOut.toByteArray(), baseOut.size(),
InetAddress.getLoopbackAddress(), deliveryServiceInternalUDPPort);
socket.send(packet);
} catch (Exception e){
//TODO - try to repeat this or something...?!?
}
}
/**
* Sends a UDP packet to the DeliveryService containing the
* Subscriptions and the mobile broker ID.
*/
synchronized public void send(AnnounceMessage msg, Set<Subscription> subs, UUID mbID){
try {
baseOut.reset();
ObjectOutputStream oos = new ObjectOutputStream(baseOut); //has to write a new header each time
oos.writeObject(msg);
oos.writeObject(subs);
oos.writeObject(mbID);
oos.flush();
//IP address of DeliveryService has to be Loopback (same maschine)
DatagramPacket packet = new DatagramPacket(baseOut.toByteArray(), baseOut.size(),
InetAddress.getLoopbackAddress(), deliveryServiceInternalUDPPort);
socket.send(packet);
} catch (Exception e){
//TODO - try to repeat this or something...?!?
}
}
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* Used for starting of the component.
* It creates an instance with the received arguments and starts a thread
* that listens on the input stream.
*/
public static void main(String[] args) {
ObjectInputStream in = null;
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(System.out);
out.flush();
in = new ObjectInputStream(System.in);
} catch (Exception e){
//don't write anything, just kill the (sub)process.
System.exit(-1);
}
int matcherID = -1;
int deliveryServiceInternalUDPPort = -1;
boolean testing = false;
boolean logWriting = false;
try {
matcherID = Integer.parseInt(args[0]);
deliveryServiceInternalUDPPort = Integer.parseInt(args[1]);
if (deliveryServiceInternalUDPPort<=1024 || deliveryServiceInternalUDPPort>49151){
throw new NumberFormatException("Given port number is <1024 or >49151 !");
}
testing = Boolean.parseBoolean(args[2]);
logWriting = Boolean.parseBoolean(args[3]);
} catch (IndexOutOfBoundsException e){
String errMsg = e.getMessage()+" Not enough arguments sent when starting Matcher! (4 needed)";
sendObject(new ErrorMessage(errMsg), out);
System.exit(-1);
} catch (NumberFormatException e){
String errMsg = e.getMessage();
sendObject(new ErrorMessage(errMsg), out);
System.exit(-1);
} catch (Exception e){
sendObject(new ErrorMessage(e.getMessage()), out);
System.exit(-1);
}
//create a new Matcher, a thread listening on System.in is
//automatically started and keeps the process alive
new Matcher(matcherID, deliveryServiceInternalUDPPort,
testing, logWriting, in, out);
sendObject(new InfoMessage("Matcher "+matcherID+" created!"), out);
}
/**
* convinience method for sending object and terminating process if
* not successfull.
*/
protected static void sendObject(Object o, ObjectOutputStream out){
try {
out.writeObject(o);
out.flush();
} catch (Exception e){
System.exit(-1);
}
}
}