package eu.play_project.dcep.distributedetalis; import static eu.play_project.dcep.constants.DcepConstants.LOG_DCEP; import static eu.play_project.dcep.constants.DcepConstants.LOG_DCEP_EXIT; import static eu.play_project.dcep.constants.DcepConstants.LOG_DCEP_FAILED_EXIT; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Properties; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.play_project.dcep.constants.DcepConstants; import eu.play_project.dcep.distributedetalis.api.EcConnectionManager; import eu.play_project.dcep.distributedetalis.api.EcConnectionmanagerException; import eu.play_project.dcep.distributedetalis.join.ResultRegistry; import eu.play_project.dcep.distributedetalis.join.SelectResults; import eu.play_project.dcep.distributedetalis.listeners.EcConnectionListenerNet; import eu.play_project.dcep.distributedetalis.persistence.Persistence; import eu.play_project.dcep.distributedetalis.persistence.PersistenceException; import eu.play_project.dcep.distributedetalis.persistence.Sqlite; import eu.play_project.dcep.distributedetalis.persistence.Sqlite.SubscriptionPerCloud; import eu.play_project.dcep.distributedetalis.utils.EventCloudHelpers; import eu.play_project.play_commons.constants.Event; import eu.play_project.play_platformservices.api.BdplQuery; import fr.inria.eventcloud.api.CompoundEvent; import fr.inria.eventcloud.api.EventCloudId; import fr.inria.eventcloud.api.PublishApi; import fr.inria.eventcloud.api.PutGetApi; import fr.inria.eventcloud.api.Quadruple; import fr.inria.eventcloud.api.SubscribeApi; import fr.inria.eventcloud.api.Subscription; import fr.inria.eventcloud.api.SubscriptionId; import fr.inria.eventcloud.api.exceptions.MalformedSparqlQueryException; import fr.inria.eventcloud.api.responses.SparqlSelectResponse; import fr.inria.eventcloud.api.wrappers.ResultSetWrapper; import fr.inria.eventcloud.exceptions.EventCloudIdNotManaged; import fr.inria.eventcloud.factories.EventCloudsRegistryFactory; import fr.inria.eventcloud.factories.ProxyFactory; public class EcConnectionManagerNet implements Serializable, EcConnectionManager { private static final long serialVersionUID = 100L; private String eventCloudRegistryUrl; private Map<String, PublishApi> outputClouds; private Map<String, SubscribeApi> inputClouds; private Map<String, PutGetApi> putGetClouds; private final Map<SubscribeApi, SubscriptionUsage> subscriptions = new HashMap<SubscribeApi, SubscriptionUsage>(); public static LinkedList<CompoundEvent> eventInputQueue; private EcConnectionListenerNet eventCloudListener; static GetEventThread getEventThread; private boolean init = false; private final Logger logger = LoggerFactory.getLogger(EcConnectionManagerNet.class);; private Persistence persistence; static final Properties constants = DcepConstants.getProperties(); public static final String REST_URI = constants.getProperty("dcep.notify.rest.local"); public EcConnectionManagerNet() { } public EcConnectionManagerNet(String eventCloudRegistry, DistributedEtalis dEtalis) throws EcConnectionmanagerException { logger.info("Initialising {}.", this.getClass().getSimpleName()); putGetClouds = new HashMap<String, PutGetApi>(); outputClouds = new HashMap<String, PublishApi>(); inputClouds = new HashMap<String, SubscribeApi>(); eventCloudRegistryUrl = eventCloudRegistry; eventInputQueue = new LinkedList<CompoundEvent>(); eventCloudListener = new EcConnectionListenerNet(); getEventThread = new GetEventThread(dEtalis); // Publish events from queue to dEtalis. new Thread(getEventThread).start(); try { final Set<EventCloudId> cloudIds = EventCloudsRegistryFactory .lookupEventCloudsRegistry(eventCloudRegistryUrl).listEventClouds(); if (cloudIds.isEmpty()) { logger.warn("No cloudIds were found in EventCloud, possible misconfiguration."); } else { for (EventCloudId cloudId : cloudIds) { logger.info("CloudId in EventCloud: " + cloudId); } } } catch (IOException e) { throw new EcConnectionmanagerException(String.format( "Error probing EventCloud registry at: '%s': %s", eventCloudRegistryUrl, e.getMessage())); } try { persistence = new Sqlite(); for (SubscriptionPerCloud sub : persistence.getSubscriptions()) { logger.info("Cleaning stale subscription from cloud {}: {}", sub.cloudId, sub.subscriptionId); try { ProxyFactory.newSubscribeProxy(eventCloudRegistryUrl, new EventCloudId(sub.cloudId)).unsubscribe(SubscriptionId.parseSubscriptionId(sub.subscriptionId)); } catch (Exception e) { logger.debug(e.getMessage()); } } } catch (PersistenceException e) { throw new EcConnectionmanagerException(e.getMessage(), e); } this.init = true; } @Override public synchronized SelectResults getDataFromCloud(String query, String cloudId) throws EcConnectionmanagerException { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } logger.info("Get data from EventCloud '" + cloudId + "' with query : " + query); PutGetApi putGetCloud; SparqlSelectResponse response = null; try { putGetCloud = getHistoricCloud(cloudId); response = putGetCloud.executeSparqlSelect(query); } catch (EcConnectionmanagerException e) { logger.error("Error while connecting to event cloud {}.", cloudId); throw e; } catch (MalformedSparqlQueryException e) { logger.error("Malformed sparql query. " + e.getMessage()); throw new EcConnectionmanagerException(e.getMessage(), e); } ResultSetWrapper rw = response.getResult(); return ResultRegistry.makeResult(rw); } /** * Persist data in historic storage. * * @param event * event containing quadruples * @param cloudId * the cloud ID to allow partitioning of storage */ @Override public void putDataInCloud(CompoundEvent event, String cloudId) throws EcConnectionmanagerException { PutGetApi putGetApi = getHistoricCloud(cloudId); for (Quadruple quad : event) { putGetApi.add(quad); } } private synchronized PutGetApi getHistoricCloud(String cloudId) throws EcConnectionmanagerException { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } try { if (!putGetClouds.containsKey(cloudId)) { PutGetApi proxy = ProxyFactory.newPutGetProxy( eventCloudRegistryUrl, new EventCloudId(eu.play_project.play_commons.constants.Stream .toTopicUri(cloudId))); putGetClouds.put(cloudId, proxy); } } catch (EventCloudIdNotManaged e) { throw new EcConnectionmanagerException(e.getMessage(), e); } catch (Exception e) { // we get various runtime exceptions here throw new EcConnectionmanagerException(e.getMessage(), e); } return putGetClouds.get(cloudId); } private synchronized SubscribeApi getInputCloud(String cloudId) throws EcConnectionmanagerException { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } try { if (!inputClouds.containsKey(cloudId)) { SubscribeApi proxy = ProxyFactory.newSubscribeProxy(eventCloudRegistryUrl, new EventCloudId(cloudId)); inputClouds.put(cloudId, proxy); } } catch (EventCloudIdNotManaged e) { throw new EcConnectionmanagerException(e.getMessage(), e); } catch (Exception e) { // we get various runtime exceptions here throw new EcConnectionmanagerException(e.getMessage(), e); } return inputClouds.get(cloudId); } private synchronized PublishApi getOutputCloud(String cloudId) throws EcConnectionmanagerException { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } try { if (!outputClouds.containsKey(cloudId)) { PublishApi proxy = ProxyFactory.newPublishProxy(eventCloudRegistryUrl, new EventCloudId(cloudId)); outputClouds.put(cloudId, proxy); } } catch (EventCloudIdNotManaged e) { throw new EcConnectionmanagerException(e.getMessage(), e); } catch (Exception e) { // we get various runtime exceptions here throw new EcConnectionmanagerException(e.getMessage(), e); } return outputClouds.get(cloudId); } @Override public void publish(CompoundEvent event) { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } String cloudId = EventCloudHelpers.getCloudId(event); if (!cloudId.isEmpty()) { try { // Do not remove this line, needed for logs. :stuehmer logger.info(LOG_DCEP_EXIT + event.getGraph() + " " + EventCloudHelpers.getMembers(event)); if (logger.isDebugEnabled()) { logger.debug(LOG_DCEP + "Complex Event:\n{}", event.toString()); } this.getOutputCloud(cloudId).publish(event); } catch (EcConnectionmanagerException e) { logger.error(LOG_DCEP_FAILED_EXIT + "Event could not be published to cloud '{}'.", cloudId); } } else { logger.warn(LOG_DCEP_FAILED_EXIT + "Got empty cloud ID from event '{}', don't know which cloud to publish to. Discarding complex event.", event.getGraph() + Event.EVENT_ID_SUFFIX); } } @Override public void registerEventPattern(BdplQuery bdplQuery) throws EcConnectionmanagerException { for (String cloudId : bdplQuery.getDetails().getInputStreams()) { subscribe(cloudId); } // Treat output streams lazily: don't connect before a complex event is // detected. } @Override public void unregisterEventPattern(BdplQuery bdplQuery) { // Deal with input streams for (String cloudId : bdplQuery.getDetails().getInputStreams()) { try { unsubscribe(cloudId, this.subscriptions.get(getInputCloud(cloudId)).sub); } catch (EcConnectionmanagerException e) { logger.error("Incurred unknown event cloud {}.", cloudId); } } // There is nothing to do for historical streams or output streams } private void subscribe(String cloudId) throws EcConnectionmanagerException { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } Subscription sub = Subscription.acceptAll(); try { if (this.subscriptions.containsKey(getInputCloud(cloudId))) { logger.info("Still subscribed to eventcloud {}.", cloudId); this.subscriptions.get(getInputCloud(cloudId)).usage++; } else { logger.info("Subscribing to eventcloud {}.", cloudId); this.getInputCloud(cloudId).subscribe(sub, eventCloudListener); this.subscriptions.put(getInputCloud(cloudId), new SubscriptionUsage(sub)); this.persistence.storeSubscription(cloudId, sub.getId().toString()); } } catch (EcConnectionmanagerException e) { logger.error("Problem subscribing to event cloud {}: {}", cloudId, e.getMessage()); throw e; } } private void unsubscribe(String cloudId, Subscription sub) { if (!init) { throw new IllegalStateException(this.getClass().getSimpleName() + " has not been initialized."); } try { if (this.subscriptions.containsKey(getInputCloud(cloudId))) { this.subscriptions.get(getInputCloud(cloudId)).usage--; if (this.subscriptions.get(getInputCloud(cloudId)).usage == 0) { logger.info("Unsubscribing from eventcloud {}.", cloudId); getInputCloud(cloudId).unsubscribe(sub.getId()); this.subscriptions.remove(getInputCloud(cloudId)); this.inputClouds.remove(cloudId); } else { logger.info("Still subscribed to eventcloud {}.", cloudId); } } } catch (EcConnectionmanagerException e) { logger.error("Problem unsubscribing from event cloud {}: {}", cloudId, e.getMessage()); } } @Override public void destroy() { logger.info("Terminating {}.", this.getClass().getSimpleName()); logger.info("Unsubscribe from Event Clouds"); // Unsubscribe for (SubscribeApi proxy : subscriptions.keySet()) { proxy.unsubscribe(subscriptions.get(proxy).sub.getId()); } persistence.deleteAllSubscriptions(); if (getEventThread != null) getEventThread.stop(); if (subscriptions != null) subscriptions.clear(); if (inputClouds != null) inputClouds.clear(); if (outputClouds != null) outputClouds.clear(); this.init = false; } /** * Usage counter for a subscription. */ private class SubscriptionUsage implements Serializable { private static final long serialVersionUID = 100L; public SubscriptionUsage(Subscription sub) { this.sub = sub; this.usage = 1; } public Subscription sub; public int usage; } /** * Take events from queue and publish them to dEtalis. * * @author sobermeier * */ public class GetEventThread implements Runnable { private final DistributedEtalis dEtalis; private volatile Thread getEventThread; public GetEventThread(DistributedEtalis dEtalis) { this.dEtalis = dEtalis; } @Override public void run() { this.getEventThread = Thread.currentThread(); while (this.getEventThread == Thread.currentThread()) { synchronized (eventInputQueue) { while (this.getEventThread == Thread.currentThread()) { if (!eventInputQueue.isEmpty()) { dEtalis.publish(eventInputQueue.poll()); } else { try { eventInputQueue.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } } } /* * See <http://docs.oracle.com/javase/1.5.0/docs/guide/misc/ * threadPrimitiveDeprecation.html> */ public void stop() { Thread stopMe = getEventThread; getEventThread = null; stopMe.interrupt(); } } }