diff --git a/modules/cpr/src/main/java/org/atmosphere/cpr/MetaBroadcaster.java b/modules/cpr/src/main/java/org/atmosphere/cpr/MetaBroadcaster.java new file mode 100644 index 00000000000..751b35f7b81 --- /dev/null +++ b/modules/cpr/src/main/java/org/atmosphere/cpr/MetaBroadcaster.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012 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.uri.UriTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Broadcast events to all or a subset of available {@link Broadcaster} based on their{@link org.atmosphere.cpr.Broadcaster#getID()} value. + * This class allow broadcasting events to a set of broadcaster that maps 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. + * + * @author Jeanfrancois Arcand + */ +public class MetaBroadcaster { + public static final String MAPPING_REGEX = "[/a-zA-Z0-9-&.*=;\\?]+"; + + private static final Logger logger = LoggerFactory.getLogger(MetaBroadcaster.class); + private final static MetaBroadcaster metaBroadcaster = new MetaBroadcaster(); + + protected List broadcast(String path, Object message) { + Collection c = BroadcasterFactory.getDefault().lookupAll(); + + final Map m = new HashMap(); + List l = new ArrayList(); + logger.debug("Map {}", path); + UriTemplate t = new UriTemplate(path); + for (Broadcaster b : c) { + logger.debug("Trying to map {} to {}", t, b.getID()); + if (t.match(b.getID(), m)) { + b.broadcast(message); + l.add(b); + } + m.clear(); + } + return l; + } + + protected List map(String path, Object message) { + + 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); + } + + /** + * Broadcast the message to all Broadcaster whose {@link org.atmosphere.cpr.Broadcaster#getID()} maps 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 + */ + public List broadcastTo(String broadcasterID, Object message) { + return map(broadcasterID, message); + } + + public final static MetaBroadcaster getDefault() { + return metaBroadcaster; + } + +} diff --git a/modules/cpr/src/test/java/org/atmosphere/cpr/MetaBroadcasterTest.java b/modules/cpr/src/test/java/org/atmosphere/cpr/MetaBroadcasterTest.java new file mode 100644 index 00000000000..2e0eb7805a9 --- /dev/null +++ b/modules/cpr/src/test/java/org/atmosphere/cpr/MetaBroadcasterTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012 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.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class MetaBroadcasterTest { + private AtmosphereConfig config; + private DefaultBroadcasterFactory factory; + + @BeforeMethod + public void setUp() throws Exception { + config = new AtmosphereFramework().getAtmosphereConfig(); + factory = new DefaultBroadcasterFactory(DefaultBroadcaster.class, "NEVER", config); + } + + @AfterMethod + public void destroy() { + factory.destroy(); + } + + @Test + public void wildcardBroadcastTest() { + factory.get("/a"); + factory.get("/b"); + factory.get("/c"); + + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/*", "yo").size(), 3); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/a/b", "yo").size(), 0); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/a", "yo").size(), 1); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/", "yo").size(), 3); + + factory.get("/*"); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/", "yo").size(), 4); + } + + @Test + public void exactBroadcastTest() { + + factory.get("/a"); + factory.get("/a/b"); + factory.get("/c"); + + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/a", "yo").get(0).getID(), "/a"); + } + + @Test + public void traillingBroadcastTest() { + + factory.get("/a/b"); + factory.get("/b"); + factory.get("/c"); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/a/b", "yo").size(), 1); + + } + + @Test + public void complexBroadcastTest() { + factory.get("/a/b/c/d"); + factory.get("/b"); + factory.get("/c"); + + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/*", "yo").size(), 3); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/a/b/c/d", "yo").size(), 1); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/a", "yo").size(), 0); + assertEquals(MetaBroadcaster.getDefault().broadcastTo("/b", "yo").size(), 1); + + } +}