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);
+
+ }
+}