package org.societies.android.platform.events;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Parcelable;
import android.util.Log;
import org.societies.android.api.comms.IMethodCallback;
import org.societies.android.api.comms.xmpp.CommunicationException;
import org.societies.android.api.comms.xmpp.XMPPError;
import org.societies.android.api.css.manager.IServiceManager;
import org.societies.android.api.events.IAndroidSocietiesEvents;
import org.societies.android.api.pubsub.ISubscriber;
import org.societies.android.platform.comms.helper.ClientCommunicationMgr;
import org.societies.android.platform.pubsub.helper.PubsubHelper;
import org.societies.api.identity.IIdentity;
import org.societies.api.identity.InvalidFormatException;
import org.societies.identity.IdentityManagerImpl;
import org.societies.utilities.DBC.Dbc;
import java.util.*;
import java.util.concurrent.CountDownLatch;
/**
* Implementation Events service
* TODO: Handle non-created pubsub nodes
*/
public class PlatformEventsBase implements IAndroidSocietiesEvents {
//Logging tag
private static final String LOG_TAG = PlatformEventsBase.class.getName();
private static final String ALL_EVENT_FILTER = "org.societies";
private static final String KEY_DIVIDER = "$";
private static final String INVALID_EVENT_PAYLOAD = "Invalid payload";
private Context androidContext;
private PubsubHelper pubsubClient = null;
//Synchronised Maps - require manual synchronisation
private final Map<String, String> subscribedToClientEvents;
private final Map<String, Integer> subscribedToEvents;
private final Map<String, String> thirdPartyEvents;
private ArrayList<ThirdPartyEventsIntents> thirdPartyEventsLookup = null;
private ArrayList<String> allPlatformEvents = null;
private String cloudNodeDestination;
private IIdentity cloudNodeIdentity;
private ClientCommunicationMgr ccm;
private boolean restrictBroadcast;
private List<String> classList;
private boolean connectedToComms;
private boolean connectedToPubsub;
private Random randomGenerator;
/**
* Default constructor
*/
public PlatformEventsBase(Context androidContext, PubsubHelper pubsubClient, ClientCommunicationMgr ccm, boolean restrictBroadcast) {
Log.d(LOG_TAG, "PlatformEventsBase created");
this.pubsubClient = pubsubClient;
this.ccm = ccm;
this.androidContext = androidContext;
this.restrictBroadcast = restrictBroadcast;
this.connectedToComms = false;
this.connectedToPubsub = false;
//tracks the events subscribed to by clients
this.subscribedToClientEvents = Collections.synchronizedMap(new HashMap<String, String>());
//tracks the events subscribed to Android Pubsub
this.subscribedToEvents = Collections.synchronizedMap(new HashMap<String, Integer>());
//tracks the events created by core services (unlikely) and 3rd party services
this.thirdPartyEvents = Collections.synchronizedMap(new HashMap<String, String>());
//a Pubsub node/Societies Intents lookup
this.thirdPartyEventsLookup = new ArrayList<PlatformEventsBase.ThirdPartyEventsIntents>();
this.cloudNodeDestination = null;
this.cloudNodeIdentity = null;
//Create random id generator
this.randomGenerator = new Random(System.currentTimeMillis());
}
@Override
public boolean startService() {
if (!this.connectedToComms && !this.connectedToPubsub) {
this.ccm.bindCommsService(new IMethodCallback() {
@Override
public void returnAction(String result) {
}
@Override
public void returnAction(boolean resultFlag) {
if (resultFlag) {
PlatformEventsBase.this.connectedToComms = true;
PlatformEventsBase.this.pubsubClient.bindPubsubService(new IMethodCallback() {
@Override
public void returnAction(String result) {
}
@Override
public void returnAction(boolean resultFlag) {
if (resultFlag) {
try {
PlatformEventsBase.this.configureForPubsub();
PlatformEventsBase.this.connectedToPubsub = true;
} catch (ClassNotFoundException e) {
Log.e(LOG_TAG, "Class Not found exception", e);
resultFlag = false;
} finally {
//Send intent
Intent intent = new Intent(IServiceManager.INTENT_SERVICE_STARTED_STATUS);
intent.putExtra(IServiceManager.INTENT_RETURN_VALUE_KEY, resultFlag);
PlatformEventsBase.this.androidContext.sendBroadcast(intent);
}
}
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
}
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
}
return false;
}
@Override
public boolean stopService() {
if (this.connectedToComms && this.connectedToPubsub) {
this.pubsubClient.unbindCommsService(new IMethodCallback() {
@Override
public void returnAction(String result) {
}
@Override
public void returnAction(boolean resultFlag) {
if (resultFlag) {
PlatformEventsBase.this.connectedToPubsub = false;
boolean result = PlatformEventsBase.this.ccm.unbindCommsService();
if (result) {
PlatformEventsBase.this.connectedToComms = false;
}
//Send intent
Intent intent = new Intent(IServiceManager.INTENT_SERVICE_STOPPED_STATUS);
intent.putExtra(IServiceManager.INTENT_RETURN_VALUE_KEY, result);
PlatformEventsBase.this.androidContext.sendBroadcast(intent);
}
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
}
return false;
}
@Override
public int getNumSubscribedNodes(String client) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Get number of subscribed to events for client: " + client);
int numListeners = 0;
synchronized (this.subscribedToClientEvents) {
for (String key : PlatformEventsBase.this.subscribedToClientEvents.keySet()) {
if (key.startsWith(client + KEY_DIVIDER)) {
numListeners++;
}
}
Intent intent = new Intent(IAndroidSocietiesEvents.NUM_EVENT_LISTENERS);
intent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, numListeners);
if (this.restrictBroadcast) {
intent.setPackage(client);
}
Log.d(LOG_TAG, "Number of subscribed events for client: " + client + " is: " + numListeners);
PlatformEventsBase.this.androidContext.sendBroadcast(intent);
Log.d(LOG_TAG, "getNumSubscribedNodes return result sent");
}
return 0;
}
@Override
public synchronized boolean publishEvent(final String client, String societiesIntent, Object eventPayload) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Event Payload must be specified", null != eventPayload);
Dbc.require("Valid Intent must be specified", null != societiesIntent && societiesIntent.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of publishEvent for client: " + client);
final Intent returnIntent = new Intent(IAndroidSocietiesEvents.PUBLISH_EVENT);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(client);
}
//check if intent is invalid. If so, signal with an exception intent otherwise proceed as normal
if (this.isIntentValid(societiesIntent, IAndroidSocietiesEvents.PUBLISH_EVENT, client, true)) {
try {
PlatformEventsBase.this.pubsubClient.publisherPublish(this.cloudNodeIdentity,
translateAndroidIntentToEvent(societiesIntent),
Integer.toString(this.randomGenerator.nextInt()),
eventPayload, new IMethodCallback() {
@Override
public void returnAction(String result) {
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, true);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Publish event return result sent");
}
@Override
public void returnAction(boolean resultFlag) {
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
} catch (XMPPError e) {
Log.e(LOG_TAG, "XMPPError", e);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Publish event return result sent");
} catch (CommunicationException e) {
Log.e(LOG_TAG, "Comunication Exception", e);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Publish event return result sent");
}
}
return false;
}
@Override
public synchronized boolean subscribeToAllEvents(String client) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of subscribeToAllEvents for client: " + client);
return this.subscribeToEvents(client, ALL_EVENT_FILTER);
}
@Override
public boolean subscribeToEvent(String client, String intent) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Valid Intent must be specified", null != intent && intent.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of subscribeToEvent for client: " + client + " and intent: " + intent);
assignConnectionParameters();
//check if intent is invalid. If so, signal with an exception intent otherwise proceed as normal
if (this.isIntentValid(intent, IAndroidSocietiesEvents.SUBSCRIBE_TO_EVENT, client, true)) {
//store client/event
synchronized (this.subscribedToClientEvents) {
Log.d(LOG_TAG, "Before size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
this.subscribedToClientEvents.put(generateClientEventKey(client, intent), intent);
Log.d(LOG_TAG, "After size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
ArrayList<String> events = new ArrayList<String>();
events.add(intent);
SubscribeToPubsub subPubSub = new SubscribeToPubsub(IAndroidSocietiesEvents.SUBSCRIBE_TO_EVENT, client, this.cloudNodeIdentity);
subPubSub.execute(events);
}
}
return false;
}
@Override
public synchronized boolean subscribeToEvents(String client, String intentFilter) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Intent filter must be specified", null != intentFilter && intentFilter.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of subscribeToEvents for client: " + client + " and filter: " + intentFilter);
assignConnectionParameters();
ArrayList<String> targetEvents = null;
String returnIntent;
//Generate a list of all possible events or a filtered subset
if (ALL_EVENT_FILTER.equals(intentFilter)) {
targetEvents = this.getAllPlatformEvents();
returnIntent = IAndroidSocietiesEvents.SUBSCRIBE_TO_ALL_EVENTS;
} else {
targetEvents = getFilteredEvents(intentFilter);
returnIntent = IAndroidSocietiesEvents.SUBSCRIBE_TO_EVENTS;
}
synchronized (this.subscribedToClientEvents) {
Log.d(LOG_TAG, "Before size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
ArrayList<String> unSubscribedEvents = new ArrayList<String>();
//add the client/intent pair if they do not already exist
for (String filteredEvent : targetEvents) {
//store client/event
if (!this.subscribedToClientEvents.containsKey(generateClientEventKey(client, filteredEvent))) {
this.subscribedToClientEvents.put(generateClientEventKey(client, filteredEvent), filteredEvent);
unSubscribedEvents.add(filteredEvent);
}
}
Log.d(LOG_TAG, "After size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
if (unSubscribedEvents.size() > 0) {
SubscribeToPubsub subPubSub = new SubscribeToPubsub(returnIntent, client, this.cloudNodeIdentity);
subPubSub.execute(unSubscribedEvents);
}
}
return false;
}
@Override
public synchronized boolean unSubscribeFromAllEvents(String client) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of unSubscribeFromAllEvents for client: " + client);
return this.unSubscribeFromEvents(client, ALL_EVENT_FILTER);
}
@Override
public synchronized boolean unSubscribeFromEvent(String client, String intent) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Valid Intent must be specified", null != intent && intent.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of unSubscribeFromEvent for client: " + client + " and intent: " + intent);
//check if intent is invalid. If so, signal with an exception intent otherwise proceed as normal
if (this.isIntentValid(intent, IAndroidSocietiesEvents.UNSUBSCRIBE_FROM_EVENT, client, true)) {
synchronized (this.subscribedToClientEvents) {
Log.d(LOG_TAG, "Before size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
//remove client/event
Log.d(LOG_TAG, "Removed value: " + this.subscribedToClientEvents.remove(generateClientEventKey(client, intent))
+ " for key: " + generateClientEventKey(client, intent));
Log.d(LOG_TAG, "After size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
ArrayList<String> events = new ArrayList<String>();
events.add(intent);
UnSubscribeFromPubsub unsubPubSub = new UnSubscribeFromPubsub(IAndroidSocietiesEvents.UNSUBSCRIBE_FROM_EVENT, client, this.cloudNodeIdentity);
unsubPubSub.execute(events);
}
}
return false;
}
@Override
public synchronized boolean unSubscribeFromEvents(String client, String intentFilter) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Intent filter must be specified", null != intentFilter && intentFilter.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of unSubscribeFromEvents for client: " + client + " and filter: " + intentFilter);
ArrayList<String> targetEvents = null;
String returnIntent;
if (ALL_EVENT_FILTER.equals(intentFilter)) {
targetEvents = this.getAllPlatformEvents();
returnIntent = IAndroidSocietiesEvents.UNSUBSCRIBE_FROM_ALL_EVENTS;
} else {
targetEvents = getFilteredEvents(intentFilter);
returnIntent = IAndroidSocietiesEvents.UNSUBSCRIBE_FROM_EVENTS;
}
synchronized (this.subscribedToClientEvents) {
ArrayList<String> subscribedEvents = new ArrayList<String>();
Log.d(LOG_TAG, "Before size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
//remove the client/intent pair if they do already exist
for (String filteredEvent : targetEvents) {
if (this.subscribedToClientEvents.containsKey(generateClientEventKey(client, filteredEvent))) {
//remove client/event
Log.d(LOG_TAG, "Removed value: " + this.subscribedToClientEvents.remove(generateClientEventKey(client, filteredEvent))
+ " for key: " + generateClientEventKey(client, filteredEvent));
subscribedEvents.add(filteredEvent);
}
}
Log.d(LOG_TAG, "After size of subscribedClientEvents: " + this.subscribedToClientEvents.size());
if (subscribedEvents.size() > 0) {
UnSubscribeFromPubsub unsubPubSub = new UnSubscribeFromPubsub(returnIntent, client, this.cloudNodeIdentity);
unsubPubSub.execute(subscribedEvents);
}
}
return false;
}
@Override
public boolean createEvent(String client, String pubsubNode, String societiesIntent) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Pubsub node must be specified", null != pubsubNode && pubsubNode.length() > 0);
Dbc.require("Societies Intent must be specified", null != societiesIntent && societiesIntent.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of createEvent for client: " + client + " and pubsub node: " + pubsubNode + " and Societies intent: " + societiesIntent);
//check if the intent is already valid, i.e. already allocated
if (!this.isIntentValid(societiesIntent, IAndroidSocietiesEvents.CREATE_EVENT, client, false)) {
//check if the Pubsub node is already valid, i.e. already allocated
if (!this.isPubsubNodeValid(pubsubNode, IAndroidSocietiesEvents.CREATE_EVENT, client, false)) {
CreateEvent invokeTask = new CreateEvent(client, this.cloudNodeIdentity, pubsubNode, societiesIntent);
invokeTask.execute();
} else {
//Create intent to signal exception
Intent returnIntent = new Intent(IAndroidSocietiesEvents.CREATE_EVENT);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_EXCEPTION_VALUE_KEY, INVALID_PUBSUB_NODE_ALREADY_EXISTS);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(client);
}
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Pubsub Node already exists return result sent");
}
} else {
//Create intent to signal exception
Intent returnIntent = new Intent(IAndroidSocietiesEvents.CREATE_EVENT);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_EXCEPTION_VALUE_KEY, INVALID_SOCIETIES_INTENT_ALREADY_EXISTS);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(client);
}
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Societies Intent already exists return result sent");
}
return false;
}
@Override
public boolean deleteEvent(String client, String pubsubNode) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Pubsub node must be specified", null != pubsubNode && pubsubNode.length() > 0);
//Invariant condition
Dbc.invariant("Comms services must be connected", this.connectedToComms && this.connectedToPubsub);
Log.d(LOG_TAG, "Invocation of deleteEvent for client: " + client + " and pubsub node: " + pubsubNode);
if (isPubsubNodeValidAndOwned(pubsubNode, IAndroidSocietiesEvents.DELETE_EVENT, client, true)) {
DeleteEvent invokeTask = new DeleteEvent(client, this.cloudNodeIdentity, pubsubNode);
invokeTask.execute();
}
return false;
}
/**
* Configure for Pubsub events
*/
private void configureForPubsub() throws ClassNotFoundException {
Log.d(LOG_TAG, "Configuring Pubsub");
//create list of event classes for Pubsub registration
this.classList = new ArrayList<String>();
Collections.addAll(this.classList, IAndroidSocietiesEvents.pubsubPayloadClasses);
this.pubsubClient.addSimpleClasses(classList);
this.pubsubClient.setSubscriberCallback(createSubscriber());
}
/**
* Create a new Subscriber object for Pubsub
*
* @return Subscriber
*/
private ISubscriber createSubscriber() {
ISubscriber subscriber = new ISubscriber() {
@Override
public void pubsubEvent(IIdentity identity, String node, String itemId, Object payload) {
Log.d(LOG_TAG, "Received Pubsub event: " + node + " itemId: " + itemId);
String intentTarget = translatePlatformEventToIntent(node);
//TODO: put in asynctask
synchronized (PlatformEventsBase.this.subscribedToClientEvents) {
for (String key : PlatformEventsBase.this.subscribedToClientEvents.keySet()) {
if (key.contains(intentTarget)) {
Intent intent = new Intent(intentTarget);
if (payload instanceof Parcelable) {
intent.putExtra(IAndroidSocietiesEvents.GENERIC_INTENT_PAYLOAD_KEY, (Parcelable) payload);
} else {
intent.putExtra(IAndroidSocietiesEvents.GENERIC_INTENT_PAYLOAD_KEY, INVALID_EVENT_PAYLOAD);
}
if (PlatformEventsBase.this.restrictBroadcast) {
intent.setPackage(getClient(key));
}
try {
PlatformEventsBase.this.androidContext.sendBroadcast(intent);
Log.d(LOG_TAG, "Android Intent " + intentTarget + " sent for client: " + getClient(key));
} catch (Exception ex) {
Log.e(LOG_TAG, "Error sending android intent " + intentTarget + " for client key: " + key, ex);
}
}
}
}
}
};
return subscriber;
}
/**
* Async task to un-register from Pubsub events
*/
private class UnSubscribeFromPubsub extends AsyncTask<List<String>, Void, Boolean> {
private boolean resultStatus = true;
private String intentValue;
private String client;
private IIdentity pubsubService;
/**
* Constructor
*
* @param intentValue
* @param client
*/
public UnSubscribeFromPubsub(String intentValue, String client, IIdentity pubsubService) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Intent filter must be specified", null != intentValue && intentValue.length() > 0);
Dbc.require("Pubsub service identity cannot be null", null != pubsubService);
Log.d(LOG_TAG, "UnSubscribeFromPubsub async task for client: " + client + " and intent: " + intentValue);
this.intentValue = intentValue;
this.client = client;
this.pubsubService = pubsubService;
}
@Override
protected Boolean doInBackground(List<String>... args) {
List<String> events = args[0];
Log.d(LOG_TAG, "Number of events to be un-subscribed: " + events.size());
Intent returnIntent = new Intent(intentValue);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(this.client);
}
try {
synchronized (PlatformEventsBase.this.subscribedToEvents) {
Log.d(LOG_TAG, "Before size of pubsubSubscribes: " + PlatformEventsBase.this.subscribedToEvents.size());
for (final String event : events) {
//Create a latch to allow each unsubscription to occur sequentially
//Failure to this caused unreliable unsubscriptions
final CountDownLatch endCondition = new CountDownLatch(1);
final long unsubscription = System.currentTimeMillis();
Integer numSubscriptions = PlatformEventsBase.this.subscribedToEvents.get(translateAndroidIntentToEvent(event));
if ((null != numSubscriptions) && (1 == numSubscriptions)) {
Log.d(LOG_TAG, "Un-subscribe from Pubsub with event : " + translateAndroidIntentToEvent(event));
PlatformEventsBase.this.subscribedToEvents.remove(translateAndroidIntentToEvent(event));
PlatformEventsBase.this.pubsubClient.subscriberUnsubscribe(this.pubsubService,
translateAndroidIntentToEvent(event),
new IMethodCallback() {
@Override
public void returnAction(String result) {
Log.d(LOG_TAG, "Pubsub un-subscription created for event: " + translateAndroidIntentToEvent(event));
Log.d(LOG_TAG, "Time to subscribe event:" + Long.toString(System.currentTimeMillis() - unsubscription));
//notify latch
endCondition.countDown();
}
@Override
public void returnAction(boolean resultFlag) {
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
} else {
PlatformEventsBase.this.subscribedToEvents.put(translateAndroidIntentToEvent(event), numSubscriptions - 1);
//notify latch
endCondition.countDown();
}
//wait for latch to release
endCondition.await();
}
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, true);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "UnSubscribeToPubsub return result sent");
}
} catch (Exception e) {
this.resultStatus = false;
Log.e(LOG_TAG, "Unable to unsubscribe for Societies events", e);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
}
return resultStatus;
}
}
/**
* Async task to register for Societies Pubsub events
*/
private class SubscribeToPubsub extends AsyncTask<List<String>, Void, Boolean> {
private String intentValue;
private String client;
private IIdentity pubsubService;
/**
* Constructor
*
* @param intentValue
* @param client
*/
public SubscribeToPubsub(String intentValue, String client, IIdentity pubsubService) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Intent filter must be specified", null != intentValue && intentValue.length() > 0);
Dbc.require("Pubsub service identity cannot be null", null != pubsubService);
Log.d(LOG_TAG, "SubscribeToPubsub async task for client: " + client + " and intent: " + intentValue);
this.intentValue = intentValue;
this.client = client;
this.pubsubService = pubsubService;
}
private boolean resultStatus = true;
@Override
protected Boolean doInBackground(List<String>... args) {
List<String> events = args[0];
Log.d(LOG_TAG, "Number of events to be subscribed: " + events.size());
Intent returnIntent = new Intent(intentValue);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(this.client);
}
try {
synchronized (PlatformEventsBase.this.subscribedToEvents) {
Log.d(LOG_TAG, "Before size of pubsubSubscribes: " + PlatformEventsBase.this.subscribedToEvents.size());
for (final String eventName : events) {
//Create a latch to allow each subscription to occur sequentially
//Failure to this caused unreliable subscription
final CountDownLatch endCondition = new CountDownLatch(1);
final long unsubscription = System.currentTimeMillis();
Integer numSubscriptions = PlatformEventsBase.this.subscribedToEvents.get(translateAndroidIntentToEvent(eventName));
if (null == numSubscriptions) {
Log.d(LOG_TAG, "Store event : " + translateAndroidIntentToEvent(eventName));
PlatformEventsBase.this.subscribedToEvents.put(translateAndroidIntentToEvent(eventName), 1);
Log.d(LOG_TAG, "Subscribe to Pubsub with event : " + translateAndroidIntentToEvent(eventName));
PlatformEventsBase.this.pubsubClient.subscriberSubscribe(this.pubsubService,
translateAndroidIntentToEvent(eventName),
new IMethodCallback() {
@Override
public void returnAction(String result) {
Log.d(LOG_TAG, "Pubsub subscription created for: " + translateAndroidIntentToEvent(eventName));
Log.d(LOG_TAG, "Time to subscribe event:" + Long.toString(System.currentTimeMillis() - unsubscription));
//notify latch
endCondition.countDown();
}
@Override
public void returnAction(boolean resultFlag) {
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
} else {
PlatformEventsBase.this.subscribedToEvents.put(translateAndroidIntentToEvent(eventName), numSubscriptions + 1);
//notify latch
endCondition.countDown();
}
//wait for latch to release
endCondition.await();
}
}
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, true);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "SubscribeToPubsub return result sent");
} catch (Exception e) {
this.resultStatus = false;
Log.e(LOG_TAG, "Unable to register for Societies events", e);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
}
return resultStatus;
}
}
/**
* Async task to create Societies Pubsub events
*/
private class CreateEvent extends AsyncTask<Void, Void, Boolean> {
private String client;
private String societiesIntent;
private IIdentity pubsubService;
private String pubsubNode;
/**
* Constructor
*
* @param client
*/
public CreateEvent(String client, IIdentity pubsubService, String pubsubNode, String societiesIntent) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Pubsub node must be specified", null != pubsubNode && pubsubNode.length() > 0);
Dbc.require("Societies Intent must be specified", null != societiesIntent && societiesIntent.length() > 0);
Dbc.require("Pubsub service identity cannot be null", null != pubsubService);
Log.d(LOG_TAG, "CreateEvent async task for client: " + client + " and pubsub node: " + pubsubNode);
this.societiesIntent = societiesIntent;
this.client = client;
this.pubsubService = pubsubService;
this.pubsubNode = pubsubNode;
}
private boolean resultStatus = true;
@Override
protected Boolean doInBackground(Void... args) {
final Intent returnIntent = new Intent(IAndroidSocietiesEvents.CREATE_EVENT);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(this.client);
}
try {
synchronized (PlatformEventsBase.this.thirdPartyEvents) {
Log.d(LOG_TAG, "Before size of thirdPartyEvents: " + PlatformEventsBase.this.thirdPartyEvents.size());
if (!PlatformEventsBase.this.thirdPartyEvents.containsKey(pubsubNode)) {
PlatformEventsBase.this.pubsubClient.ownerCreate(pubsubService, pubsubNode, new IMethodCallback() {
@Override
public void returnAction(String result) {
if (null != result) {
PlatformEventsBase.this.thirdPartyEvents.put(pubsubNode, client);
ThirdPartyEventsIntents thirdPartyEvent = new ThirdPartyEventsIntents(pubsubNode, societiesIntent);
PlatformEventsBase.this.thirdPartyEventsLookup.add(thirdPartyEvent);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, true);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Create event return result sent");
}
}
@Override
public void returnAction(boolean resultFlag) {
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
} else {
this.resultStatus = false;
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Create event return result sent");
}
}
} catch (Exception e) {
this.resultStatus = false;
Log.e(LOG_TAG, "Unable to create event for Societies events", e);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
}
return resultStatus;
}
}
/**
* Async task to delete Societies Pubsub events
*/
private class DeleteEvent extends AsyncTask<Void, Void, Boolean> {
private String client;
private IIdentity pubsubService;
private String pubsubNode;
/**
* Constructor
*
* @param client
*/
public DeleteEvent(String client, IIdentity pubsubService, String pubsubNode) {
Dbc.require("Client subscriber must be specified", null != client && client.length() > 0);
Dbc.require("Pubsub node must be specified", null != pubsubNode && pubsubNode.length() > 0);
Dbc.require("Pubsub service identity cannot be null", null != pubsubService);
Log.d(LOG_TAG, "DeleteEvent async task for client: " + client + " and pubsub node: " + pubsubNode);
this.client = client;
this.pubsubService = pubsubService;
this.pubsubNode = pubsubNode;
}
private boolean resultStatus = true;
@Override
protected Boolean doInBackground(Void... args) {
final Intent returnIntent = new Intent(IAndroidSocietiesEvents.DELETE_EVENT);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(this.client);
}
try {
synchronized (PlatformEventsBase.this.thirdPartyEvents) {
Log.d(LOG_TAG, "Before size of thirdPartyEvents: " + PlatformEventsBase.this.thirdPartyEvents.size());
if (PlatformEventsBase.this.thirdPartyEvents.containsKey(pubsubNode) &&
PlatformEventsBase.this.thirdPartyEvents.get(pubsubNode).equals(client)) {
PlatformEventsBase.this.pubsubClient.ownerDelete(pubsubService, pubsubNode, new IMethodCallback() {
@Override
public void returnAction(String result) {
if (null != result) {
PlatformEventsBase.this.thirdPartyEvents.remove(pubsubNode);
PlatformEventsBase.this.removeThirdPartyEvent(pubsubNode);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, true);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Delete event return result sent");
}
}
@Override
public void returnAction(boolean resultFlag) {
}
@Override
public void returnException(String result) {
// TODO Auto-generated method stub
}
});
} else {
this.resultStatus = false;
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Create event return result sent");
}
}
} catch (Exception e) {
this.resultStatus = false;
Log.e(LOG_TAG, "Unable to create event for Societies events", e);
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
}
return resultStatus;
}
}
/**
* Assign connection parameters (must happen after successful XMPP login)
*/
private void assignConnectionParameters() {
Log.d(LOG_TAG, "assignConnectionParameters invoked");
if (null == cloudNodeDestination) {
try {
this.cloudNodeDestination = this.ccm.getIdManager().getCloudNode().getJid();
Log.d(LOG_TAG, "Domain Authority Node: " + this.cloudNodeDestination);
try {
this.cloudNodeIdentity = IdentityManagerImpl.staticfromJid(this.cloudNodeDestination);
Log.d(LOG_TAG, "Domain Authority identity: " + this.cloudNodeIdentity);
} catch (InvalidFormatException e) {
Log.e(LOG_TAG, "Unable to get Domain Authority identity", e);
}
} catch (InvalidFormatException i) {
Log.e(LOG_TAG, "ID Manager exception", i);
}
}
}
/**
* Retrieve a list of events that match the filter. If a Societies intent starts with filter, the event will be included
*
* @param filter
* @return ArrayList<String> of filtered events
*/
private static ArrayList<String> getFilteredEvents(String filter) {
ArrayList<String> filteredEvents = new ArrayList<String>();
for (String event : IAndroidSocietiesEvents.societiesAndroidIntents) {
if (event.startsWith(filter)) {
filteredEvents.add(event);
}
}
return filteredEvents;
}
/**
* Retrieve a list of all platform events
*
* @return ArrayList<String> List of all Platform events
*/
private ArrayList<String> getAllPlatformEvents() {
if (null == this.allPlatformEvents) {
this.allPlatformEvents = getFilteredEvents(ALL_EVENT_FILTER);
}
return this.allPlatformEvents;
}
/**
* Generate the Map key for client/event pair
*
* @param client
* @param event
* @return
*/
private static String generateClientEventKey(String client, String event) {
return client + KEY_DIVIDER + event;
}
/**
* Translate Societies platform inter-node Pubsub event to an internal Android Societies internal intent.
* Uses the two Events/Intents arrays to maps inter-node events to Android equivalent intents
* Also uses the Third party created events array of events.
*
* @param platformEvent
* @return String Android Societies internal intent
*/
private String translatePlatformEventToIntent(String platformEvent) {
String retValue = null;
for (int i = 0; i < IAndroidSocietiesEvents.societiesAndroidEvents.length; i++) {
if (platformEvent.equals(IAndroidSocietiesEvents.societiesAndroidEvents[i])) {
retValue = IAndroidSocietiesEvents.societiesAndroidIntents[i];
break;
}
}
if (null == retValue) {
for (ThirdPartyEventsIntents event : this.thirdPartyEventsLookup) {
if (event.getSocietiesIntent().equals(platformEvent)) {
retValue = event.getSocietiesIntent();
}
}
}
return retValue;
}
/**
* Translate Societies Android Pubsub intent to an inter-node Societies platform Pubsub event.
* Uses the two Events/Intents arrays to maps Android intents to Societies equivalent Pubsub events.
* Also uses the Third party created events array of events.
*
* @param androidIntent
* @return String Pubsub inter-node event
*/
private String translateAndroidIntentToEvent(String androidIntent) {
String retValue = null;
for (int i = 0; i < IAndroidSocietiesEvents.societiesAndroidIntents.length; i++) {
if (androidIntent.equals(IAndroidSocietiesEvents.societiesAndroidIntents[i])) {
retValue = IAndroidSocietiesEvents.societiesAndroidEvents[i];
break;
}
}
if (null == retValue) {
for (ThirdPartyEventsIntents event : this.thirdPartyEventsLookup) {
if (event.getSocietiesIntent().equals(androidIntent)) {
retValue = event.getPubsubNode();
}
}
}
return retValue;
}
/**
* Retrieve client part of key
*
* @param key
* @return client part of key
*/
private static String getClient(String key) {
return key.substring(0, key.indexOf(KEY_DIVIDER));
}
/**
* Determine if an event is contained in a set of events
*
* @param keys
* @param event
* @return boolean true if event is contained
*/
private static boolean isValueEquals(Set<String> keys, String event) {
boolean retValue = false;
for (String key : keys) {
if (key.equals(event)) {
retValue = true;
break;
}
}
return retValue;
}
/**
* Is a specified Pubsub node valid
*
* @param node
* @param serviceIntent current service's return value intent
* @param client
* @param sendIntent should a return intent be sent ?
* @return boolean
*/
private boolean isPubsubNodeValid(String node, String serviceIntent, String client, boolean sendIntent) {
boolean retValue = false;
for (String validNode : IAndroidSocietiesEvents.societiesAndroidEvents) {
if (validNode.equals(node)) {
retValue = true;
break;
}
}
synchronized (PlatformEventsBase.this.thirdPartyEvents) {
if (!retValue) {
for (ThirdPartyEventsIntents event : this.thirdPartyEventsLookup) {
if (event.getPubsubNode().equals(node)) {
retValue = true;
break;
}
}
}
}
//Create intent to signal exception
if (!retValue && sendIntent) {
Intent returnIntent = new Intent(serviceIntent);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_EXCEPTION_VALUE_KEY, INVALID_PUBSUB_NODE);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(client);
}
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Invalid Pubsub node return result sent");
}
return retValue;
}
/**
* Is a specified Pubsub node valid and owned by client wishing to delete it ?
*
* @param node
* @param serviceIntent current service's return value intent
* @param client
* @param sendIntent should a return intent be sent ?
* @return boolean
*/
private boolean isPubsubNodeValidAndOwned(String node, String serviceIntent, String client, boolean sendIntent) {
boolean retValue = false;
synchronized (PlatformEventsBase.this.thirdPartyEvents) {
if (PlatformEventsBase.this.thirdPartyEvents.get(node).equals(client)) {
retValue = true;
}
}
//Create intent to signal exception
if (!retValue && sendIntent) {
Intent returnIntent = new Intent(serviceIntent);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_EXCEPTION_VALUE_KEY, INVALID_PUBSUB_NODE);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(client);
}
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Invalid Pubsub node return result sent");
}
return retValue;
}
/**
* Is a specified intent a valid , recognised intent
*
* @param intent Societies intent corresponding to a Pubsub node
* @param intent current service's return value intent
* @param client
* @param sendIntent should a return intent be sent ?
* @return boolean true if valid
*/
private boolean isIntentValid(String intent, String serviceIntent, String client, boolean sendIntent) {
boolean retValue = false;
for (String validIntent : IAndroidSocietiesEvents.societiesAndroidIntents) {
if (validIntent.equals(intent)) {
retValue = true;
break;
}
}
synchronized (PlatformEventsBase.this.thirdPartyEvents) {
if (!retValue) {
for (ThirdPartyEventsIntents event : this.thirdPartyEventsLookup) {
if (event.getSocietiesIntent().equals(intent)) {
retValue = true;
break;
}
}
}
}
//Create intent to signal exception
if (!retValue && sendIntent) {
Intent returnIntent = new Intent(serviceIntent);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_RETURN_VALUE_KEY, false);
returnIntent.putExtra(IAndroidSocietiesEvents.INTENT_EXCEPTION_VALUE_KEY, INVALID_SOCIETIES_INTENT);
if (PlatformEventsBase.this.restrictBroadcast) {
returnIntent.setPackage(client);
}
PlatformEventsBase.this.androidContext.sendBroadcast(returnIntent);
Log.d(LOG_TAG, "Invalid Societies Intent return result sent");
}
return retValue;
}
/**
* Utility class that allow an association between a Pubsub node and Societies Android intent
*/
private class ThirdPartyEventsIntents {
private String pubsubNode;
private String societiesIntent;
public ThirdPartyEventsIntents(String pubsubNode, String societiesEvent) {
this.pubsubNode = pubsubNode;
this.societiesIntent = societiesEvent;
}
public String getPubsubNode() {
return pubsubNode;
}
public void setPubsubNode(String pubsubNode) {
this.pubsubNode = pubsubNode;
}
public String getSocietiesIntent() {
return societiesIntent;
}
public void setSocietiesIntent(String societiesIntent) {
this.societiesIntent = societiesIntent;
}
}
/**
* Remove a {@link ThirdPartyEventsIntents} object from the array of created objects
*
* @param pubsubNode
*/
private void removeThirdPartyEvent(String pubsubNode) {
for (Iterator<ThirdPartyEventsIntents> iter = this.thirdPartyEventsLookup.iterator(); iter.hasNext(); ) {
if (iter.next().getPubsubNode().equals(pubsubNode)) {
iter.remove();
break;
}
}
}
}