/**
* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Properties;
import org.openiot.cupus.common.UniqueObject;
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.MobileBrokerDisconnectMessage;
import org.openiot.cupus.message.external.MobileBrokerRegisterMessage;
import org.openiot.cupus.message.external.PublishMessage;
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.message.internal.ErrorMessage;
import org.openiot.cupus.message.internal.InfoMessage;
import org.openiot.cupus.message.internal.InitialAnnouncementMatchesMessage;
import org.openiot.cupus.message.internal.InitialMatchesMessage;
import org.openiot.cupus.message.internal.StartComponentMessage;
import org.openiot.cupus.util.LogWriter;
/**
* This is the main cloud-broker class. It represents the cloud-broker which is
* instantiated, started and stopped through it.
* The CloudBroker object starts the three broker components (MessageReceiver,
* DeliveryService and (initial) Matcher) as separate processes and relays the
* communication between them by forwarding from input streams to the proper
* output streams (for example reading the in from MessageReceiver and sending it to
* the out of the DeliveryService for communication between those two components)
*
* @author Eugen Rozic
*
*/
public class CloudBroker {
private String brokerName;
private String brokerIP;
private int brokerPort;
private int internalUDPPort;
private int queueCapacity;
private LogWriter log;
private boolean logWriting = true;
private boolean testing = false;
private String classpath = null;
private Process messageReceiver = null;
private MessageReceiverRelay messageReceiverRelay = null;
private Process deliveryService = null;
private DeliveryServiceRelay deliveryServiceRelay = null;
private int numberOfMatchers;
private Process[] matchers;
private MatcherRelay[] matcherRelays;
private int matcherRoundRobin = 0;
/**
* The main and only constructor. It takes in a configuration file
* that specifies all information the broker needs, the broker name and port.
*
* It then creates the needed components and starts the threads that control
* the information flow from and to them.
*
* @param configFile
*/
public CloudBroker(File configFile, String classpath) {
this.classpath = classpath;
//reads properties and instantiates and sets everything...
try {
Properties brokerProps = new Properties();
FileInputStream fileIn = new FileInputStream(configFile);
brokerProps.load(fileIn);
fileIn.close();
this.brokerName = brokerProps.getProperty("brokerName");
if (this.brokerName==null)
throw new NullPointerException("Name must be defined!");
this.brokerPort = Integer.parseInt(brokerProps.getProperty("brokerPort"));
this.internalUDPPort = Integer.parseInt(
brokerProps.getProperty("internalUDPPort"));
this.brokerIP = UniqueObject.getLocalIP();
this.queueCapacity = Integer.parseInt(brokerProps.getProperty("queueCapacity"));
this.numberOfMatchers = Integer.parseInt(
brokerProps.getProperty("numberOfMatchers"));
if (brokerProps.getProperty("testing", "false").toLowerCase().equals("false")){
this.testing = false;
} else if (brokerProps.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 (brokerProps.getProperty("logWriting", "true").toLowerCase().equals("true")){
this.logWriting = true;
} else if (brokerProps.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.brokerName + ".log", logWriting, testing);
try {
initMessageReceiver();
initDeliveryService();
initMatchers();
} catch (Exception e){
e.printStackTrace();
shutdown();
}
log.writeToLog("Broker name: " + this.brokerName, true);
log.writeToLog("Broker port: " + this.brokerPort, true);
if (brokerIP.equals("")) {
log.writeToLog("Broker IP address: unidentifiable?!", true);
} else {
log.writeToLog("Broker IP address: "+this.brokerIP, true);
}
log.writeToLog("",true); //empty line
}
/**
* Sends the start messages to the MessageReceiver and the DeliveryService
* components of the broker so they can start accepting accepting and processing
* requests and sending answers to them.
*/
public void start(){
messageReceiverRelay.sendStartMessage();
deliveryServiceRelay.sendStartMessage();
}
/**
* Kills the broker.
* It does so by just terminating the process which will automatically
* cause all output streams to close which will in turn cause all input
* streams on the receiving side to throw an EOFException which will
* cause the starting of shutdown of all the components (subprocesses)
* this CloudBroker is connected to.
*/
public void shutdown(){
log.writeToLog("Shutting down all Broker components!", true);
System.exit(-1);
}
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* Starts the MessageReceiver process and a thread to manage it's input/output.
*/
private void initMessageReceiver() throws IOException {
String[] cmd = new String[]{"java", "-cp", classpath,
MessageReceiver.class.getName(),
brokerName, brokerIP, Integer.toString(brokerPort),
Boolean.toString(testing), Boolean.toString(logWriting)};
ProcessBuilder builder = new ProcessBuilder(cmd).directory(new File(".")).redirectErrorStream(true);
messageReceiver = builder.start();
messageReceiverRelay = new MessageReceiverRelay();
new Thread(messageReceiverRelay).start();
messageReceiverRelay.out.flush();
}
/**
* Starts the DeliveryService process and a thread to manage it's input/output.
*/
private void initDeliveryService() throws IOException {
String[] cmd = new String[]{"java", "-cp", classpath,
DeliveryService.class.getName(),
brokerName, brokerIP, Integer.toString(internalUDPPort),
Integer.toString(queueCapacity),
Boolean.toString(testing), Boolean.toString(logWriting)};
ProcessBuilder builder = new ProcessBuilder(cmd).directory(new File(".")).redirectErrorStream(true);
deliveryService = builder.start();
deliveryServiceRelay = new DeliveryServiceRelay();
new Thread(deliveryServiceRelay).start();
}
/**
* Starts the Matcher processes and a thread for each to manage it's input/output.
*/
private void initMatchers() throws IOException {
matchers = new Process[numberOfMatchers];
matcherRelays = new MatcherRelay[numberOfMatchers];
for (int matcherID=0; matcherID<numberOfMatchers; matcherID++){
String[] cmd = new String[]{"java", "-cp", classpath,
Matcher.class.getName(), Integer.toString(matcherID),
Integer.toString(internalUDPPort),
Boolean.toString(testing), Boolean.toString(logWriting)};
ProcessBuilder builder = new ProcessBuilder(cmd).directory(new File(".")).redirectErrorStream(true);
matchers[matcherID] = builder.start();
MatcherRelay matcherRelay = new MatcherRelay(matcherID);
matcherRelays[matcherID] = matcherRelay;
new Thread(matcherRelay).start();
matcherRelay.out.flush();
}
}
/**
* Convinience method to send a message to an output stream...
*/
private void sendMessage(Object msg, ObjectOutputStream out){
try {
out.writeObject(msg);
out.flush();
} catch (IOException e){
log.error("Sending message failed: "+e.getMessage());
this.shutdown();
}
}
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* Manages the in/out streams of the messageReceiver subprocess...
*/
private class MessageReceiverRelay implements Runnable {
ObjectInputStream in;
ObjectOutputStream out;
public MessageReceiverRelay() {
try {
// catches both the stdout and stderr of the subprocess
out = new ObjectOutputStream(
messageReceiver.getOutputStream());
out.flush();
in = new ObjectInputStream(messageReceiver.getInputStream());
} catch (Exception e){
log.error(e.getMessage());
e.printStackTrace();
CloudBroker.this.shutdown();
}
}
@Override
public void run() {
while (true){
Object objIn = null;
try {
objIn = in.readObject();
} catch (Exception e){
log.error("Unable to read from MessageReceiver's input stream! "+e.getMessage());
CloudBroker.this.shutdown();
return;
}
if (objIn instanceof InternalMessage){
if (objIn instanceof InitialMatchesMessage){
//forward to deliveryService to send the initial notify msgs
sendMessage(objIn, deliveryServiceRelay.out);
} else if (objIn instanceof InitialAnnouncementMatchesMessage){
//forward to deliveryService to send the initial announcement msgs
sendMessage(objIn, deliveryServiceRelay.out);
} else if (objIn instanceof ErrorMessage){
log.error(((ErrorMessage)objIn).getContents());
} else if (objIn instanceof InfoMessage) {
log.writeToLog(((InfoMessage)objIn).getContents());
} else {
log.error("Unexpected internal message received from MessageReceiver - "+objIn.getClass().getName());
}
} else if (objIn instanceof Message){
//System.out.println("CloudBroker!" + objIn.getClass());
if (objIn instanceof SubscriberRegisterMessage){
//forward to deliveryService to establish a connection
sendMessage(objIn, deliveryServiceRelay.out);
} else if (objIn instanceof SubscriberDisconnectMessage){
//forward to deliveryService to kill the queue
sendMessage(objIn, deliveryServiceRelay.out);
} else if (objIn instanceof SubscriberUnregisterMessage){
//forward to deliveryService to kill and remove the queue
sendMessage(objIn, deliveryServiceRelay.out);
//forward to all Matchers to remove all subscribers subscriptions
for (int i=0; i<numberOfMatchers; i++){
sendMessage(objIn, matcherRelays[i].out);
}
} else if (objIn instanceof MobileBrokerRegisterMessage){
//forward to deliveryService to establish a connection
sendMessage(objIn, deliveryServiceRelay.out);
} else if (objIn instanceof MobileBrokerDisconnectMessage){
//forward to deliveryService to kill the queue
sendMessage(objIn, deliveryServiceRelay.out);
} else if (objIn instanceof SubscribeMessage){
SubscribeMessage msg = (SubscribeMessage)objIn;
log.writeToLog("Received "
+(msg.isUnsubscribe()?"unsubscription ":"subscription ")
+ msg.getSubscription()+".");
//forward to a matcher to process...
sendMessage(objIn, matcherRelays[matcherRoundRobin].out);
matcherRoundRobin = (matcherRoundRobin+1)%numberOfMatchers;
} else if (objIn instanceof PublishMessage){
PublishMessage msg = (PublishMessage)objIn;
log.writeToLog("Received "
+(msg.isUnpublish()?"unpublication ":"publication ")
+ msg.getPublication()+".");
//forward to all matchers to process...
for (int i=0; i<numberOfMatchers; i++){
sendMessage(objIn, matcherRelays[i].out);
}
} else if (objIn instanceof AnnounceMessage){
AnnounceMessage msg = (AnnounceMessage)objIn;
log.writeToLog("Received "
+(msg.isRevokeAnnouncement()?"revoke announcement ":"announcement ")
+ msg.getAnnouncement()+".");
//forward to all matchers to process...
for (int i=0; i<numberOfMatchers; i++){
sendMessage(objIn, matcherRelays[i].out);
}
} else {
log.error("Unexpected external message received from MessageReceiver - "+objIn.getClass().getName());
}
} else {
log.error("Object received from the MessageReceiver is not of class Message" +
" or InternalMessage! ("+objIn.getClass().getName()+" instead: "+objIn.toString()+" ). Ignoring...");
}
}
}
/**
* Sends a start message to the MessageReceiver components of the cloud broker.
* It should make the MessageReceiver start the thread that listens for
* incoming connection requests from subscribers and publishers.
*/
void sendStartMessage(){
try {
out.writeObject(new StartComponentMessage());
out.flush();
} catch (IOException e){
//if messageReceiver found terminated shut everything down...
log.error("MessageReceiver found dead while sending the start message.");
CloudBroker.this.shutdown();
}
}
}
/**
* Manages the in/out streams of the deliveryService subprocess...
*/
private class DeliveryServiceRelay implements Runnable {
ObjectInputStream in;
ObjectOutputStream out;
public DeliveryServiceRelay() {
try {
// catches both the stdout and stderr of the subprocess
out = new ObjectOutputStream(
deliveryService.getOutputStream());
out.flush();
in = new ObjectInputStream(
deliveryService.getInputStream());
} catch (Exception e){
log.error(e.getMessage());
e.printStackTrace();
CloudBroker.this.shutdown();
}
}
@Override
public void run() {
while (true){
Object objIn = null;
try {
objIn = in.readObject();
} catch (Exception e){
log.error("Unable to read from DeliveryService's input stream! "+e.getMessage());
CloudBroker.this.shutdown();
return;
}
if (objIn instanceof InternalMessage){
if (objIn instanceof ErrorMessage){
log.error(((ErrorMessage)objIn).getContents());
} else if (objIn instanceof InfoMessage) {
log.writeToLog(((InfoMessage)objIn).getContents());
} else {
log.error("Unexpected internal message received from DeliveryService - "+objIn.getClass().getName());
}
} else if (objIn instanceof Message){
if (objIn instanceof SubscriberDisconnectMessage){
log.writeToLog("Subscriber "+((SubscriberDisconnectMessage)objIn).getEntityName()+
" disconnected from broker!");
//forward to messageReceiver to kill the SubForBroker
sendMessage(objIn, messageReceiverRelay.out);
} else {
log.error("Unexpected external message received from DeliveryService - "+objIn.getClass().getName());
}
} else {
log.error("Object received from the DeliveryService is not of class Message" +
" or InternalMessage! ("+objIn.getClass().getName()+" instead). Ignoring...");
}
}
}
/**
* Sends a start message to the DeliveryService components of the cloud broker.
* It should make the DeliveryService start the thread that listens for
* incoming connection requests from subscribers.
*/
void sendStartMessage(){
try {
out.writeObject(new StartComponentMessage());
out.flush();
} catch (IOException e){
//if deliveryService found terminated shut everything down...
log.error("DeliveryService found dead while sending the start message.");
CloudBroker.this.shutdown();
}
}
}
/**
* Manages the in/out streams of the rootMatcher subprocess...
*/
private class MatcherRelay implements Runnable {
ObjectInputStream in;
ObjectOutputStream out;
private int matcherID;
public MatcherRelay(int matcherID) {
this.matcherID = matcherID;
try {
// catches both the stdout and stderr of the subprocess
out = new ObjectOutputStream(
matchers[matcherID].getOutputStream());
out.flush();
in = new ObjectInputStream(
matchers[matcherID].getInputStream());
} catch (Exception e){
log.error(e.getMessage());
e.printStackTrace();
CloudBroker.this.shutdown();
}
}
@Override
public void run() {
while (true){
Object objIn = null;
try {
objIn = in.readObject();
} catch (Exception e){
log.error("Unable to read from Matcher "+matcherID+"'s input stream! "+e.getMessage());
CloudBroker.this.shutdown();
return;
}
if (objIn instanceof InternalMessage){
if (objIn instanceof ErrorMessage){
log.error(((ErrorMessage)objIn).getContents());
} else if (objIn instanceof InfoMessage) {
log.writeToLog(((InfoMessage)objIn).getContents());
} else {
log.error("Unexpected internal message received from RootMatcher - "+objIn.getClass().getName());
}
} else if (objIn instanceof Message){
log.error("Unexpected external message received from root Matcher - "+objIn.getClass().getName());
} else {
log.error("Object received from the root Matcher is not of class Message" +
" or InternalMessage! ("+objIn.getClass().getName()+" instead). Ignoring...");
}
}
}
}
}