All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.atmosphere.cpr.MetaBroadcaster Maven / Gradle / Ivy

There is a newer version: 3.0.5.slf4jvaadin1
Show newest version
/*
 * Copyright 2014 Jean-Francois Arcand
 *
 * 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.
 */
package org.atmosphere.cpr;

import org.atmosphere.util.ExecutorsFactory;
import org.atmosphere.util.uri.UriTemplate;
import com.vaadin.external.org.slf4j.Logger;
import com.vaadin.external.org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Broadcast events to all or a subset of available {@link Broadcaster}s based on their {@link org.atmosphere.cpr.Broadcaster#getID()} value.
 * This class allows broadcasting events to a set of broadcasters that maps to some String like:
 * 
 *        // Broadcast the event to all Broadcaster ID starting with /hello
 *        broadcast("/hello", event)
 *        // Broadcast the event to all Broadcaster ID
 *        broaccast("/*", event);
 * 
* The rule used is similar to path/URI mapping used by technology like Servlet, Jersey, etc. *

* NOTE: Broadcasters' name must start with / in order to get retrieved by this class. *

* This class is NOT thread safe. *

* If you want to use MetaBroadcaster with Jersey or any framework, make sure all {@link org.atmosphere.cpr.Broadcaster#getID()} * starts with '/'. For example, with Jersey: *

 *
 * @author Jeanfrancois Arcand
 * @Path(RestConstants.STREAMING + "/workspace{wid:/[0-9A-Z]+}")
 * public class JerseyPubSub {
 * @PathParam("wid") private Broadcaster topic;
 * 
*/ public class MetaBroadcaster { public static final String MAPPING_REGEX = "[/a-zA-Z0-9-&.*=@_;\\?]+"; private final static Logger logger = LoggerFactory.getLogger(MetaBroadcaster.class); private static MetaBroadcaster metaBroadcaster; private final static ConcurrentLinkedQueue broadcasterListeners = new ConcurrentLinkedQueue(); private final static MetaBroadcasterFuture E = new MetaBroadcasterFuture(Collections.emptyList()); private MetaBroadcasterCache cache = new NoCache(); private AtmosphereConfig config; public MetaBroadcaster() { // Ugly metaBroadcaster = this; } public MetaBroadcaster(AtmosphereConfig config) { this.config = config; // Ugly metaBroadcaster = this; } protected MetaBroadcasterFuture broadcast(final String path, Object message, int time, TimeUnit unit, boolean delay, boolean cacheMessage) { if (config != null || BroadcasterFactory.getDefault() != null) { Collection c = config != null ? config.getBroadcasterFactory().lookupAll() : BroadcasterFactory.getDefault().lookupAll(); final Map m = new HashMap(); List l = new ArrayList(); logger.trace("Map {}", path); UriTemplate t = null; try { t = new UriTemplate(path); for (Broadcaster b : c) { logger.trace("Trying to map {} to {}", t, b.getID()); if (t.match(b.getID(), m)) { l.add(b); } m.clear(); } } finally { if (t != null) t.destroy(); } if (l.isEmpty() && cacheMessage) { if (NoCache.class.isAssignableFrom(cache.getClass())) { logger.warn("No Broadcaster matches {}. Message {} WILL BE LOST. " + "Make sure you cache it or make sure the Broadcaster exists before.", path, message); } else { cache.cache(path, message); } return E; } MetaBroadcasterFuture f = new MetaBroadcasterFuture(l); CompleteListener cl = new CompleteListener(f); for (Broadcaster b : l) { if (time <= 0) { f.outerFuture(b.addBroadcasterListener(cl).broadcast(message)); } else if (!delay) { f.outerFuture(b.scheduleFixedBroadcast(message, time, unit)); } else { f.outerFuture(b.delayBroadcast(message, time, unit)); } } return f; } else { return E; } } protected MetaBroadcasterFuture map(String path, Object message, int time, TimeUnit unit, boolean delay, boolean cacheMessage) { if (path == null || path.isEmpty()) { throw new NullPointerException(); } if (!path.startsWith("/")) { path = "/" + path; } if (path.contains("*")) { path = path.replace("*", MAPPING_REGEX); } if (path.equals("/")) { path += MAPPING_REGEX; } return broadcast(path, message, time, unit, delay, cacheMessage); } /** * Broadcast the message to all Broadcasters whose {@link org.atmosphere.cpr.Broadcaster#getID()} matches the broadcasterID value. * * @param broadcasterID a String (or path) that can potentially match a {@link org.atmosphere.cpr.Broadcaster#getID()} * @param message a message to be broadcasted * @return a Future */ public Future> broadcastTo(String broadcasterID, Object message) { return map(broadcasterID, message, -1, null, false, true); } /** * Flush the cached messages. * @return this */ protected MetaBroadcaster flushCache() { if (cache != null) cache.flushCache(); return this; } /** * Broadcast the message at a fixed rate to all Broadcasters whose {@link org.atmosphere.cpr.Broadcaster#getID()} * matches the broadcasterID value. This operation will invoke {@link Broadcaster#scheduleFixedBroadcast(Object, long, java.util.concurrent.TimeUnit)}} * * @param broadcasterID a String (or path) that can potentially match a {@link org.atmosphere.cpr.Broadcaster#getID()} * @param message a message to be broadcasted * @param time a time value * @param unit a {@link TimeUnit} * @return a Future */ public Future> scheduleTo(String broadcasterID, Object message, int time, TimeUnit unit) { return map(broadcasterID, message, time, unit, false, true); } /** * Delay the message delivery to Broadcasters whose {@link org.atmosphere.cpr.Broadcaster#getID()} * matches the broadcasterID value. This operation will invoke {@link Broadcaster#delayBroadcast(Object, long, java.util.concurrent.TimeUnit)} (Object, long, java.util.concurrent.TimeUnit)}} * * @param broadcasterID a String (or path) that can potentially match a {@link org.atmosphere.cpr.Broadcaster#getID()} * @param message a message to be broadcasted * @param time a time value * @param unit a {@link TimeUnit} * @return a Future */ public Future> delayTo(String broadcasterID, Object message, int time, TimeUnit unit) { return map(broadcasterID, message, time, unit, true, true); } /** * * @deprecated Use {@link AtmosphereConfig#metaBroadcaster()} */ public synchronized final static MetaBroadcaster getDefault() { if (metaBroadcaster == null) { metaBroadcaster = new MetaBroadcaster(); } return metaBroadcaster; } private final static class CompleteListener extends BroadcasterListenerAdapter { private final MetaBroadcasterFuture f; private CompleteListener(MetaBroadcasterFuture f) { this.f = f; } @Override public void onPostCreate(Broadcaster b) { } @Override public void onComplete(Broadcaster b) { b.removeBroadcasterListener(this); f.countDown(); if (f.isDone()) { for (BroadcasterListener l : broadcasterListeners) { try { l.onComplete(b); } catch (Exception ex) { logger.warn("", ex); } } } } @Override public void onPreDestroy(Broadcaster b) { } } private final static class MetaBroadcasterFuture implements Future> { private final CountDownLatch latch; private final List l; private boolean isCancelled = false; private final List> outerFuture = new ArrayList>(); private MetaBroadcasterFuture(List l) { this.latch = new CountDownLatch(l.size()); this.l = l; } MetaBroadcasterFuture outerFuture(Future f) { outerFuture.add(f); return this; } @Override public boolean cancel(boolean b) { for (Future f : outerFuture) { f.cancel(b); } while (latch.getCount() > 0) { latch.countDown(); } isCancelled = true; return isCancelled; } @Override public boolean isCancelled() { return isCancelled; } @Override public boolean isDone() { return latch.getCount() == 0; } @Override public List get() throws InterruptedException, ExecutionException { latch.await(); return l; } @Override public List get(long t, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException { latch.await(t, timeUnit); return l; } public void countDown() { latch.countDown(); } } /** * Add a {@link BroadcasterListener} to all mapped {@link Broadcaster}s. * * @param b {@link BroadcasterListener} * @return this */ public MetaBroadcaster addBroadcasterListener(BroadcasterListener b) { broadcasterListeners.add(b); return this; } /** * Remove the {@link BroadcasterListener}. * * @param b {@link BroadcasterListener} * @return this */ public MetaBroadcaster removeBroadcasterListener(BroadcasterListener b) { broadcasterListeners.remove(b); return this; } /** * Set the {@link MetaBroadcasterCache}. Default is {@link NoCache}. * @param cache * @return */ public MetaBroadcaster cache(MetaBroadcasterCache cache) { this.cache = cache; return this; } protected void destroy(){ broadcasterListeners.clear(); flushCache(); } /** * Cache message if no {@link Broadcaster} maps the {@link #broadcastTo(String, Object)} */ public static interface MetaBroadcasterCache { /** * Cache the Broadcaster ID and message * @param path the value passed to {@link #broadcastTo(String, Object)} * @param message the value passed to {@link #broadcastTo(String, Object)} * @return this */ public MetaBroadcasterCache cache(String path, Object message); /** * Flush the Cache. * @return this */ public MetaBroadcasterCache flushCache(); } public final static class NoCache implements MetaBroadcasterCache { @Override public MetaBroadcasterCache cache(String path, Object o) { return this; } @Override public MetaBroadcasterCache flushCache() { return this; } } /** * Flush the cache every 30 seconds. */ public final static class ThirtySecondsCache implements MetaBroadcasterCache, Runnable { private final MetaBroadcaster metaBroadcaster; private final ConcurrentHashMap cache = new ConcurrentHashMap(); public ThirtySecondsCache(MetaBroadcaster metaBroadcaster, AtmosphereConfig config) { this.metaBroadcaster = metaBroadcaster; ExecutorsFactory.getScheduler(config).scheduleAtFixedRate(this, 0, 30, TimeUnit.SECONDS); } @Override public MetaBroadcasterCache cache(String path, Object o) { cache.put(path, o); return this; } @Override public MetaBroadcasterCache flushCache() { for (Map.Entry e : cache.entrySet()) { metaBroadcaster.map(e.getKey(), e.getValue(), -1, null, false, false); } return this; } @Override public void run() { flushCache(); cache.clear(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy