package ch.unifr.pai.twice.comm.serverPush.server; /* * Copyright 2013 Oliver Schmid * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.Broadcaster; import org.atmosphere.cpr.BroadcasterFactory; import ch.unifr.pai.twice.comm.serverPush.shared.PingEvent; /** * Server-side event processing logic * * @author Oliver Schmid * */ public class EventProcessing { private static Map<AtmosphereResource, Long> lastEventOfAtmosphereResource = Collections.synchronizedMap(new HashMap<AtmosphereResource, Long>()); private static Map<ServerRemoteEvent, Broadcaster> waitingBlockingEvents = Collections.synchronizedMap(new HashMap<ServerRemoteEvent, Broadcaster>()); private static Map<String, AtmosphereResource> uuidToResource = Collections.synchronizedMap(new HashMap<String, AtmosphereResource>()); private static int waitForEventsInMs = 0; /** * Track the last event of all connected clients. * * @param event * @param sender */ private void updateLastEventOfAtmospherResource(ServerRemoteEvent event, AtmosphereResource sender) { if (event.getOriginUUID() != null) uuidToResource.put(event.getOriginUUID(), sender); Long eventTimestamp = event.getTimestampAsLong(); if (eventTimestamp != null) { Long lastEventTimestamp = lastEventOfAtmosphereResource.get(sender); if (lastEventTimestamp == null || lastEventTimestamp.longValue() < eventTimestamp.longValue()) lastEventOfAtmosphereResource.put(sender, eventTimestamp); } } /** * Process the received message (order and distribute it to the appropriate clients). * * @param message * @param sender */ public void processMessage(Object message, final AtmosphereResource sender) { System.out.println("Process message: " + message); final ServerRemoteEvent e = new ServerRemoteEvent(message); updateLastEventOfAtmospherResource(e, sender); processBlockingEvents(); // Do not process ping events if (e.getType() == null || !e.getType().equals(PingEvent.class.getName())) { long executeInMs = waitForEventsInMs - e.getDelay(); if (executeInMs < 0) executeInMs = 0; ScheduledExecutorService svc = Executors.newSingleThreadScheduledExecutor(); svc.schedule(new Runnable() { @Override public void run() { Broadcaster b = BroadcasterFactory.getDefault().lookup(e.getContext() != null ? e.getContext() : AtmosphereHandler.GLOBALBROADCASTERID); if (b != null) { if (e.isBlockingEvent()) { Set<AtmosphereResource> missingRessources = broadcastBlockingEvents(e, b); if (missingRessources != null) { waitingBlockingEvents.put(e, b); String ping = getJSON(); for (AtmosphereResource res : missingRessources) { b.broadcast(ping, res); } } } else { if (e.getReceipients() != null && e.getReceipients().size() > 0) { Set<AtmosphereResource> receipients = new HashSet<AtmosphereResource>(); for (String r : e.getReceipients()) { AtmosphereResource resource = uuidToResource.get(r); if (resource != null) receipients.add(resource); } b.broadcast(e.getMessage(), receipients); } // Other events are broadcasted to the other clients // only, because the sender handles its native event // by itself already. else { b.broadcast(e.getMessage(), excludeSender(b, sender)); } } } } }, executeInMs, TimeUnit.MILLISECONDS); } } /** * Iterate through the blocking events on hold and send those which can be sent. */ private void processBlockingEvents() { Set<ServerRemoteEvent> broadcasted = new HashSet<ServerRemoteEvent>(); for (ServerRemoteEvent event : waitingBlockingEvents.keySet()) { if (broadcastBlockingEvents(event, waitingBlockingEvents.get(event)) == null) { broadcasted.add(event); } } for (ServerRemoteEvent r : broadcasted) { waitingBlockingEvents.remove(r); } } /** * Check if the blocking events in the queue are ready to be fired (if all clients have confirmed that they do not have conflicting events anymore). * * @param event * @param b * @return */ private Set<AtmosphereResource> broadcastBlockingEvents(ServerRemoteEvent event, Broadcaster b) { Long eventTimestamp = event.getTimestampAsLong(); if (eventTimestamp == null) { throw new RuntimeException("Event sent without valid timestamp!"); } Set<AtmosphereResource> missingResources = new HashSet<AtmosphereResource>(); for (AtmosphereResource r : b.getAtmosphereResources()) { Long broadcasterLastEvent = lastEventOfAtmosphereResource.get(r); if (broadcasterLastEvent == null || eventTimestamp > broadcasterLastEvent) { missingResources.add(r); } } if (missingResources.size() == 0) b.broadcast(event.getMessage()); return missingResources.size() > 0 ? missingResources : null; } /** * Exclude the sender of a message from the broadcasting (this prevents that the sender receives the messages which are originated by himself) * * @param b * @param sender * @return */ private Set<AtmosphereResource> excludeSender(Broadcaster b, AtmosphereResource sender) { Set<AtmosphereResource> subset = new HashSet<AtmosphereResource>(); for (AtmosphereResource r : b.getAtmosphereResources()) { if (!r.equals(sender)) subset.add(r); } return subset; } /** * @return the JSON string for the PING-event */ public static String getJSON() { return "{\"t\":\"" + new Date().getTime() + "\", \"T\":\"" + PingEvent.class.getName() + "\", \"data\":{\"instanceid\":\"ping\"}}"; } }