/** * 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 com.google.android.gcm.server.Result; import com.sun.corba.se.pept.broker.Broker; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.UUID; import org.apache.commons.codec.binary.Base64; import org.openiot.cupus.common.MinimalistLinkedHashQueue; import org.openiot.cupus.message.Message; /** * This class is an implementation of a queue assigned to a single subscriber * that is registered to the broker and it's job is to hold the outgoing * messages to that subscriber and send them when able (i.e. when connection * exists). * * @author Eugen * */ public class SubscriberQueue implements Runnable { private int CAPACITY = -1; private MinimalistLinkedHashQueue<Message> queue; private Object queueMutex = new Object(); private UUID entityID; private Socket socket; private ObjectOutputStream outToSubscriber; private boolean isGCMEntity; private String GCMId; private DeliveryService broker; private boolean isConnected = false; public SubscriberQueue(UUID entityID, int queueCapacity, DeliveryService broker, boolean GCMEntity) { this.entityID = entityID; this.CAPACITY = queueCapacity; this.broker = broker; queue = new MinimalistLinkedHashQueue<Message>(CAPACITY); this.isGCMEntity = GCMEntity; } @Override public void run() { try { if (isGCMEntity) { if (GCMId == null ||GCMId.isEmpty()) { return; } } else { if (socket == null || socket.isClosed()) { return; } } isConnected = true; while (isConnected && broker.isRunning) { if (!isGCMEntity) { if (outToSubscriber == null) { break; } } synchronized (queueMutex) { Message next = queue.peek(); if (next == null) { try { queueMutex.wait(); //block it until something for sending comes along... } catch (InterruptedException e) { /* ignore (something will have already called disconnect if something is wrong * with the connection, and if not the thread will just go another cycle... */ } } else { if (sendMessage(next)) //if the message is successfully sent then remove it from queue { queue.poll(); } } } } } catch (Exception e) { terminateConnection(); } } /** * Putting matching publications in the queue to send to subscriber. (avoids * putting duplicates in because of MinimalistLinkedHashQueue * implementation) */ public boolean put(Message msg) { synchronized (queueMutex) { if (!queue.offer(msg)) { if (queue.isFull()) { broker.informBroker("Entity ID=" + entityID + " not notified of publication or subscription" + " " + msg + " because queue is full!", true); } return false; } else { queueMutex.notify(); //let the sending thread know there is something new in the queue return true; } } } /** * Used for unpublishing yet unsent publications... */ public boolean remove(Message msg) { if (msg == null) { return false; } synchronized (queueMutex) { return queue.remove(msg); } } /** * Method for sending messages.<br> * (terminates connection if sending fails) * * @param msgToSend Message to be sent */ private boolean sendMessage(Message msgToSend) { if (isGCMEntity) { String forDelivery = Base64.encodeBase64String(serialize(msgToSend)); com.google.android.gcm.server.Message message = new com.google.android.gcm.server.Message.Builder() .delayWhileIdle(true) .addData("message", forDelivery) .build(); //TODO: serialization + base64 try { if (broker.getSender() != null) { Result r = broker.getSender().send(message, GCMId, 1); broker.informBroker("GCM Message: " + r.getMessageId(), false); return true; } else { broker.informBroker("GCM Sender is not initialized - missing application key", false); return false; } } catch (Exception e) { broker.informBroker("GCM Sender cannot send message to a mobile broker " + e.getMessage(), false); return false; } } else { try { outToSubscriber.writeObject(msgToSend); outToSubscriber.flush(); } catch (Exception e) { //SubscriberForBroker will have already sent a disconnect message terminateConnection(); return false; } return true; } } /** * For stoping the thread that sends messages from this queue... */ public void terminateConnection() { if (isGCMEntity) { isConnected = false; } else { if (socket == null && !isConnected) { return; } try { if (socket != null) { socket.close(); } } catch (Exception e) { //ignoring } socket = null; outToSubscriber = null; isConnected = false; } } /** * For setting up a new connection with a previously registered subscriber * (existing queue). It disconnects the queue first (if it was connected, * and it shouldn't have been. */ public void setConnection(Socket socket) { try { isConnected = false; terminateConnection(); this.socket = socket; this.outToSubscriber = new ObjectOutputStream( socket.getOutputStream()); outToSubscriber.flush(); } catch (IOException ex) { outToSubscriber = null; try { socket.close(); } catch (IOException ex1) { } } } public void setGCMId(String id) { this.GCMId = id; } public byte[] serialize(Object obj) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream os = new ObjectOutputStream(out); os.writeObject(obj); return out.toByteArray(); } catch (IOException e) { broker.informBroker("Cannot serialize a message", false); return new byte[0]; } } public boolean isConnected() { return isConnected; } public UUID getEntityID() { return entityID; } }