You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2018/02/05 09:29:37 UTC

[3/9] cayenne git commit: CAY-2403

CAY-2403


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/ab1fd0bf
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/ab1fd0bf
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/ab1fd0bf

Branch: refs/heads/master
Commit: ab1fd0bf471954b94aba1aeda0c149175de50929
Parents: 023547e
Author: Arseni Bulatski <an...@gmail.com>
Authored: Fri Feb 2 15:24:03 2018 +0300
Committer: Arseni Bulatski <an...@gmail.com>
Committed: Fri Feb 2 15:24:03 2018 +0300

----------------------------------------------------------------------
 RELEASE-NOTES.txt                               |   1 +
 cayenne-jgroups/pom.xml                         |  82 +++++
 .../org/apache/cayenne/event/JGroupsModule.java |  63 ++++
 .../event/JGroupsServerModuleProvider.java      |  50 +++
 .../apache/cayenne/event/JavaGroupsBridge.java  | 231 ++++++++++++++
 .../cayenne/event/JavaGroupsBridgeFactory.java  |  49 +++
 .../cayenne/event/JavaGroupsBridgeProvider.java |  50 +++
 ...iguration.server.CayenneServerModuleProvider |  20 ++
 .../event/CayenneJGroupsModuleProviderTest.java |  36 +++
 .../event/JavaGroupsBridgeFactoryTest.java      |  84 +++++
 .../event/JavaGroupsBridgeProviderTest.java     |  97 ++++++
 cayenne-jms/pom.xml                             |  81 +++++
 .../org/apache/cayenne/event/JMSBridge.java     | 280 +++++++++++++++++
 .../apache/cayenne/event/JMSBridgeFactory.java  |  39 +++
 .../apache/cayenne/event/JMSBridgeProvider.java |  50 +++
 .../org/apache/cayenne/event/JMSModule.java     |  54 ++++
 .../cayenne/event/JMSServerModuleProvider.java  |  50 +++
 ...iguration.server.CayenneServerModuleProvider |  20 ++
 .../event/CayenneJMSModuleProviderTest.java     |  36 +++
 .../cayenne/event/JMSBridgeFactoryTest.java     |  77 +++++
 .../cayenne/event/JMSBridgeProviderTest.java    |  87 ++++++
 cayenne-xmpp/pom.xml                            |  86 ++++++
 .../org/apache/cayenne/event/XMPPBridge.java    | 308 +++++++++++++++++++
 .../apache/cayenne/event/XMPPBridgeFactory.java |  41 +++
 .../cayenne/event/XMPPBridgeProvider.java       |  50 +++
 .../org/apache/cayenne/event/XMPPModule.java    |  71 +++++
 .../cayenne/event/XMPPServerModuleProvider.java |  50 +++
 ...iguration.server.CayenneServerModuleProvider |  20 ++
 .../event/CayenneXMPPModuleProviderTest.java    |  36 +++
 .../cayenne/event/XMPPBridgeFactoryTest.java    |  72 +++++
 .../cayenne/event/XMPPBridgeProviderTest.java   | 105 +++++++
 .../apache/cayenne/event/XMPPBridgeTest.java    |  54 ++++
 eventbridges/cayenne-jgroups/pom.xml            |  41 ---
 .../org/apache/cayenne/event/JGroupsModule.java |  63 ----
 .../event/JGroupsServerModuleProvider.java      |  50 ---
 .../apache/cayenne/event/JavaGroupsBridge.java  | 231 --------------
 .../cayenne/event/JavaGroupsBridgeFactory.java  |  49 ---
 .../cayenne/event/JavaGroupsBridgeProvider.java |  50 ---
 ...iguration.server.CayenneServerModuleProvider |  20 --
 .../event/CayenneJGroupsModuleProviderTest.java |  36 ---
 .../event/JavaGroupsBridgeFactoryTest.java      |  84 -----
 .../event/JavaGroupsBridgeProviderTest.java     |  97 ------
 eventbridges/cayenne-jms/pom.xml                |  41 ---
 .../org/apache/cayenne/event/JMSBridge.java     | 280 -----------------
 .../apache/cayenne/event/JMSBridgeFactory.java  |  39 ---
 .../apache/cayenne/event/JMSBridgeProvider.java |  50 ---
 .../org/apache/cayenne/event/JMSModule.java     |  54 ----
 .../cayenne/event/JMSServerModuleProvider.java  |  50 ---
 ...iguration.server.CayenneServerModuleProvider |  20 --
 .../event/CayenneJMSModuleProviderTest.java     |  36 ---
 .../cayenne/event/JMSBridgeFactoryTest.java     |  77 -----
 .../cayenne/event/JMSBridgeProviderTest.java    |  87 ------
 eventbridges/cayenne-xmpp/pom.xml               |  46 ---
 .../org/apache/cayenne/event/XMPPBridge.java    | 308 -------------------
 .../apache/cayenne/event/XMPPBridgeFactory.java |  41 ---
 .../cayenne/event/XMPPBridgeProvider.java       |  50 ---
 .../org/apache/cayenne/event/XMPPModule.java    |  71 -----
 .../cayenne/event/XMPPServerModuleProvider.java |  50 ---
 ...iguration.server.CayenneServerModuleProvider |  20 --
 .../event/CayenneXMPPModuleProviderTest.java    |  36 ---
 .../cayenne/event/XMPPBridgeFactoryTest.java    |  72 -----
 .../cayenne/event/XMPPBridgeProviderTest.java   | 105 -------
 .../apache/cayenne/event/XMPPBridgeTest.java    |  54 ----
 eventbridges/pom.xml                            |  82 -----
 pom.xml                                         |   4 +-
 65 files changed, 2433 insertions(+), 2391 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 746012c..dbf604a 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -21,6 +21,7 @@ CAY-2393 Add sqlserver-docker profile to automate tests on SQLServer
 CAY-2394 Upgrade to Apache Velocity 2.0
 CAY-2395 cdbimport: add option to create project file
 CAY-2396 Upgrade maven-assembly-plugin to 3.1.0
+CAY-2403 Extract eventbridges to top level
 
 Bug Fixes:
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/pom.xml b/cayenne-jgroups/pom.xml
new file mode 100644
index 0000000..9698a38
--- /dev/null
+++ b/cayenne-jgroups/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>cayenne-parent</artifactId>
+        <groupId>org.apache.cayenne</groupId>
+        <version>4.1.M2-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>cayenne-jgroups</artifactId>
+    <name>cayenne-jgroups: Cayenne JGroups Event bridge</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>jgroups</groupId>
+            <artifactId>jgroups-all</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- This ensures LICENSE and NOTICE inclusion in all jars -->
+            <plugin>
+                <artifactId>maven-remote-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsModule.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsModule.java b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsModule.java
new file mode 100644
index 0000000..77738c3
--- /dev/null
+++ b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsModule.java
@@ -0,0 +1,63 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.MapBuilder;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.0
+ */
+public class JGroupsModule implements Module {
+
+    /**
+     * A DI container key for the Map&lt;String, String&gt; storing
+     * {@link org.apache.cayenne.event.JavaGroupsBridge} properties
+     *
+     * @since 4.0
+     */
+    public static final String JAVA_GROUPS_BRIDGE_PROPERTIES_MAP = "cayenne.server.java_group_bridge";
+
+    public static void contributeMulticastAddress(Binder binder, String address) {
+        contributeProperties(binder).put(JavaGroupsBridge.MCAST_ADDRESS_PROPERTY, address);
+    }
+
+    public static void contributeMulticastPort(Binder binder, int port) {
+        contributeProperties(binder).put(JavaGroupsBridge.MCAST_PORT_PROPERTY, Integer.toString(port));
+    }
+
+    public static void contributeConfigUrl(Binder binder, String config) {
+        contributeProperties(binder).put(JavaGroupsBridge.JGROUPS_CONFIG_URL_PROPERTY, config);
+    }
+
+    private static MapBuilder<String> contributeProperties(Binder binder) {
+        return binder.bindMap(String.class, JAVA_GROUPS_BRIDGE_PROPERTIES_MAP);
+    }
+
+    @Override
+    public void configure(Binder binder) {
+        // init properties' defaults
+        contributeMulticastAddress(binder, JavaGroupsBridge.MCAST_ADDRESS_DEFAULT);
+        contributeMulticastPort(binder, JavaGroupsBridge.MCAST_PORT_DEFAULT_INT);
+
+        binder.bind(EventBridge.class).toProvider(JavaGroupsBridgeProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsServerModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsServerModuleProvider.java b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsServerModuleProvider.java
new file mode 100644
index 0000000..60c5bcd
--- /dev/null
+++ b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JGroupsServerModuleProvider.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.configuration.server.CayenneServerModuleProvider;
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.0
+ */
+public class JGroupsServerModuleProvider implements CayenneServerModuleProvider {
+
+    @Override
+    public Module module() {
+        return new JGroupsModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return JGroupsModule.class;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Collection<Class<? extends Module>> overrides() {
+        Collection modules = Collections.singletonList(ServerModule.class);
+        return modules;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridge.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridge.java b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridge.java
new file mode 100644
index 0000000..cd7bae2
--- /dev/null
+++ b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridge.java
@@ -0,0 +1,231 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.jgroups.Channel;
+import org.jgroups.JChannel;
+import org.jgroups.Message;
+import org.jgroups.MessageListener;
+import org.jgroups.blocks.PullPushAdapter;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Implementation of EventBridge that passes and receives events via JavaGroups
+ * communication software.
+ * 
+ * @since 1.1
+ */
+public class JavaGroupsBridge extends EventBridge implements MessageListener {
+
+    public static final String MCAST_ADDRESS_DEFAULT = "228.0.0.5";
+    public static final int MCAST_PORT_DEFAULT_INT = 22222;
+    public static final String MCAST_PORT_DEFAULT = Integer.toString(MCAST_PORT_DEFAULT_INT);
+
+    public static final String MCAST_ADDRESS_PROPERTY = "cayenne.JavaGroupsBridge.mcast.address";
+    public static final String MCAST_PORT_PROPERTY = "cayenne.JavaGroupsBridge.mcast.port";
+
+    /**
+     * Defines a property for JavaGroups XML configuration file.
+     */
+    public static final String JGROUPS_CONFIG_URL_PROPERTY = "javagroupsbridge.config.url";
+
+    // TODO: Meaning of "state" in JGroups is not yet clear to me
+    protected byte[] state;
+
+    protected Channel channel;
+    protected PullPushAdapter adapter;
+    protected String multicastAddress;
+    protected String multicastPort;
+    protected String configURL;
+
+    /**
+     * Creates new instance of JavaGroupsBridge.
+     */
+    public JavaGroupsBridge(EventSubject localSubject, String externalSubject) {
+        super(localSubject, externalSubject);
+    }
+
+    /**
+     * @since 1.2
+     */
+    public JavaGroupsBridge(Collection<EventSubject> localSubjects, String externalSubject) {
+        super(localSubjects, externalSubject);
+    }
+
+    /**
+     * @since 4.0
+     */
+    public JavaGroupsBridge(Collection<EventSubject> localSubjects, String externalSubject, Map<String, String> properties) {
+        super(localSubjects, externalSubject);
+
+        // configure properties
+        String multicastAddress = properties.get(MCAST_ADDRESS_PROPERTY);
+        String multicastPort = properties.get(MCAST_PORT_PROPERTY);
+
+        this.configURL = properties.get(JGROUPS_CONFIG_URL_PROPERTY);
+        this.multicastAddress = (multicastAddress != null) ? multicastAddress : MCAST_ADDRESS_DEFAULT;
+        this.multicastPort = (multicastPort != null) ? multicastPort : MCAST_PORT_DEFAULT;
+    }
+
+    public String getConfigURL() {
+        return configURL;
+    }
+
+    public void setConfigURL(String configURL) {
+        this.configURL = configURL;
+    }
+
+    public String getMulticastAddress() {
+        return multicastAddress;
+    }
+
+    public void setMulticastAddress(String multicastAddress) {
+        this.multicastAddress = multicastAddress;
+    }
+
+    public String getMulticastPort() {
+        return multicastPort;
+    }
+
+    public void setMulticastPort(String multicastPort) {
+        this.multicastPort = multicastPort;
+    }
+
+    public byte[] getState() {
+        return state;
+    }
+
+    public void setState(byte[] state) {
+        this.state = state;
+    }
+
+    /**
+     * Implementation of org.javagroups.MessageListener - a callback method to process
+     * incoming messages.
+     */
+    public void receive(Message message) {
+        try {
+            CayenneEvent event = messageObjectToEvent((Serializable) message.getObject());
+            if (event != null) {
+
+                onExternalEvent(event);
+            }
+        }
+        catch (Exception ex) {
+            // TODO: Andrus, 2/8/2006 logging... Log4J was removed to make this usable on
+            // the client
+        }
+    }
+
+    @Override
+    protected void startupExternal() throws Exception {
+        // TODO: need to do more research to figure out the best default transport
+        // settings
+        // to avoid fragmentation, etc.
+
+        // if config file is set, use it, otherwise use a default
+        // set of properties, trying to configure multicast address and port
+        if (configURL != null) {
+            channel = new JChannel(configURL);
+        }
+        else {
+            String configString = buildConfigString();
+            channel = new JChannel(configString);
+        }
+
+        // Important - discard messages from self
+        channel.setOpt(Channel.LOCAL, Boolean.FALSE);
+        channel.connect(externalSubject);
+
+        if (receivesExternalEvents()) {
+            adapter = new PullPushAdapter(channel, this);
+        }
+    }
+
+    /**
+     * Creates JavaGroups configuration String, using preconfigured multicast port and
+     * address.
+     */
+    protected String buildConfigString() {
+        if (multicastAddress == null) {
+            throw new IllegalStateException("'multcastAddress' is not set");
+        }
+
+        if (multicastPort == null) {
+            throw new IllegalStateException("'multcastPort' is not set");
+        }
+
+        return "UDP(mcast_addr="
+                + multicastAddress
+                + ";mcast_port="
+                + multicastPort
+                + ";ip_ttl=32):"
+                + "PING(timeout=3000;num_initial_members=6):"
+                + "FD(timeout=3000):"
+                + "VERIFY_SUSPECT(timeout=1500):"
+                + "pbcast.NAKACK(gc_lag=10;retransmit_timeout=600,1200,2400,4800):"
+                + "pbcast.STABLE(desired_avg_gossip=10000):"
+                + "FRAG:"
+                + "pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;"
+                + "shun=true;print_local_addr=false)";
+    }
+
+    @Override
+    protected void shutdownExternal() throws Exception {
+        try {
+            if (adapter != null) {
+                adapter.stop();
+            }
+
+            channel.close();
+        }
+        finally {
+            adapter = null;
+            channel = null;
+        }
+    }
+
+    @Override
+    protected void sendExternalEvent(CayenneEvent localEvent) throws Exception {
+        Message message = new Message(null, null, eventToMessageObject(localEvent));
+        channel.send(message);
+    }
+
+    /**
+     * Converts CayenneEvent to a serializable object that will be sent via JMS. Default
+     * implementation simply returns the event, but subclasses can customize this
+     * behavior.
+     */
+    protected Serializable eventToMessageObject(CayenneEvent event) throws Exception {
+        return event;
+    }
+
+    /**
+     * Converts a Serializable instance to CayenneEvent. Returns null if the object is not
+     * supported. Default implementation simply tries to cast the object to CayenneEvent,
+     * but subclasses can customize this behavior.
+     */
+    protected CayenneEvent messageObjectToEvent(Serializable object) throws Exception {
+        return (object instanceof CayenneEvent) ? (CayenneEvent) object : null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeFactory.java b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeFactory.java
new file mode 100644
index 0000000..302e5ca
--- /dev/null
+++ b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeFactory.java
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Factory to create JavaGroupsBridge instances. If JavaGroups library is not installed
+ * this factory will return a noop EventBridge as a failover mechanism.
+ * <p/>
+ * For further information about JavaGroups consult the <a href="http://www.jgroups.org/">documentation</a>.
+ *
+ * @since 1.1
+ */
+public class JavaGroupsBridgeFactory implements EventBridgeFactory {
+
+    /**
+     * Creates a JavaGroupsBridge instance. Since JavaGroups is not shipped with Cayenne
+     * and should be installed separately, a common misconfiguration problem may be the
+     * absence of JavaGroups jar file. This factory returns a dummy noop EventBridge, if
+     * this is the case. This would allow the application to continue to run, but without
+     * remote notifications.
+     */
+    public EventBridge createEventBridge(
+            Collection<EventSubject> localSubjects,
+            String externalSubject,
+            Map<String, String> properties) {
+        return new JavaGroupsBridge(localSubjects, externalSubject, properties);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeProvider.java b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeProvider.java
new file mode 100644
index 0000000..983aa7f
--- /dev/null
+++ b/cayenne-jgroups/src/main/java/org/apache/cayenne/event/JavaGroupsBridgeProvider.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.access.DataDomain;
+import org.apache.cayenne.access.DataRowStore;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.di.DIRuntimeException;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Provider;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class JavaGroupsBridgeProvider implements Provider<EventBridge> {
+
+    @Inject
+    protected DataDomain dataDomain;
+
+    @Inject(JGroupsModule.JAVA_GROUPS_BRIDGE_PROPERTIES_MAP)
+    Map<String, String> properties;
+
+    @Override
+    public EventBridge get() throws DIRuntimeException {
+        EventSubject snapshotEventSubject = EventSubject.getSubject(DataRowStore.class, dataDomain.getName());
+
+        return new JavaGroupsBridge(
+                Collections.singleton(snapshotEventSubject),
+                EventBridge.convertToExternalSubject(snapshotEventSubject),
+                properties);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider b/cayenne-jgroups/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
new file mode 100644
index 0000000..b6c6632
--- /dev/null
+++ b/cayenne-jgroups/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
@@ -0,0 +1,20 @@
+##################################################################
+#   Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you 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.
+##################################################################
+
+org.apache.cayenne.event.JGroupsServerModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/test/java/org/apache/cayenne/event/CayenneJGroupsModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/test/java/org/apache/cayenne/event/CayenneJGroupsModuleProviderTest.java b/cayenne-jgroups/src/test/java/org/apache/cayenne/event/CayenneJGroupsModuleProviderTest.java
new file mode 100644
index 0000000..320d490
--- /dev/null
+++ b/cayenne-jgroups/src/test/java/org/apache/cayenne/event/CayenneJGroupsModuleProviderTest.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.configuration.server.CayenneServerModuleProvider;
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+/**
+ * @since 4.0
+ */
+public class CayenneJGroupsModuleProviderTest {
+
+    @Test
+    public void testAutoLoadable() {
+        ModuleProviderChecker.testProviderPresent(JGroupsServerModuleProvider.class, CayenneServerModuleProvider.class);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeFactoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeFactoryTest.java b/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeFactoryTest.java
new file mode 100644
index 0000000..694dc24
--- /dev/null
+++ b/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeFactoryTest.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ */
+public class JavaGroupsBridgeFactoryTest {
+
+    protected Collection<EventSubject> subjects = Collections.singleton(new EventSubject("test"));
+    protected String externalSubject = "subject";
+
+    @Test
+    public void testCreateEventBridge() throws Exception {
+        EventBridge bridge = new JavaGroupsBridgeFactory().createEventBridge(
+                subjects,
+                externalSubject,
+                Collections.<String, String>emptyMap());
+
+        assertNotNull(bridge);
+        assertTrue(bridge instanceof JavaGroupsBridge);
+        assertEquals(subjects, bridge.getLocalSubjects());
+        assertEquals(externalSubject, bridge.getExternalSubject());
+    }
+
+    @Test
+    public void testUseProperties() throws Exception {
+        JavaGroupsBridgeFactory bridgeFactory = new JavaGroupsBridgeFactory();
+
+        Map<String, String> properties = new HashMap<>();
+        properties.put(JavaGroupsBridge.MCAST_ADDRESS_PROPERTY, JavaGroupsBridgeProviderTest.MCAST_ADDRESS_TEST);
+        properties.put(JavaGroupsBridge.MCAST_PORT_PROPERTY, JavaGroupsBridgeProviderTest.MCAST_PORT_TEST);
+        properties.put(JavaGroupsBridge.JGROUPS_CONFIG_URL_PROPERTY, JavaGroupsBridgeProviderTest.CONFIG_URL_TEST);
+
+        JavaGroupsBridge bridge = (JavaGroupsBridge) bridgeFactory.createEventBridge(
+                subjects,
+                externalSubject,
+                properties);
+
+        assertEquals(bridge.getMulticastAddress(), JavaGroupsBridgeProviderTest.MCAST_ADDRESS_TEST);
+        assertEquals(bridge.getMulticastPort(), JavaGroupsBridgeProviderTest.MCAST_PORT_TEST);
+        assertEquals(bridge.getConfigURL(), JavaGroupsBridgeProviderTest.CONFIG_URL_TEST);
+    }
+
+    @Test
+    public void testUseDefaultProperties() throws Exception {
+        JavaGroupsBridgeFactory bridgeFactory = new JavaGroupsBridgeFactory();
+        JavaGroupsBridge bridge = (JavaGroupsBridge) bridgeFactory.createEventBridge(
+                subjects,
+                externalSubject,
+                Collections.<String, String>emptyMap());
+
+        assertEquals(bridge.getMulticastAddress(), JavaGroupsBridge.MCAST_ADDRESS_DEFAULT);
+        assertEquals(bridge.getMulticastPort(), JavaGroupsBridge.MCAST_PORT_DEFAULT);
+        assertEquals(bridge.getConfigURL(), null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeProviderTest.java b/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeProviderTest.java
new file mode 100644
index 0000000..00232d4
--- /dev/null
+++ b/cayenne-jgroups/src/test/java/org/apache/cayenne/event/JavaGroupsBridgeProviderTest.java
@@ -0,0 +1,97 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.access.DataDomain;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.log.Slf4jJdbcEventLogger;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.tx.DefaultTransactionFactory;
+import org.apache.cayenne.tx.DefaultTransactionManager;
+import org.apache.cayenne.tx.TransactionFactory;
+import org.apache.cayenne.tx.TransactionManager;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class JavaGroupsBridgeProviderTest {
+
+    private final DataDomain DOMAIN = new DataDomain("test");
+    private final EventManager EVENT_MANAGER = new DefaultEventManager();
+    protected static final String MCAST_ADDRESS_TEST = "192.168.0.0";
+    protected static final String MCAST_PORT_TEST = "1521";
+    protected static final String CONFIG_URL_TEST = "somehost.com";
+
+    @Test
+    public void testGetJavaGroupsBridge() throws Exception {
+        Injector injector = DIBootstrap.createInjector(new DefaultBindings(), new JGroupsModule());
+        EventBridge bridge = injector.getInstance(EventBridge.class);
+
+        assertNotNull(bridge);
+        assertTrue(bridge instanceof JavaGroupsBridge);
+    }
+
+    @Test
+    public void testUseProperties() throws Exception {
+        Module module = binder -> {
+            JGroupsModule.contributeMulticastAddress(binder, MCAST_ADDRESS_TEST);
+            JGroupsModule.contributeMulticastPort(binder, Integer.parseInt(MCAST_PORT_TEST));
+            JGroupsModule.contributeConfigUrl(binder, CONFIG_URL_TEST);
+        };
+
+        Injector injector = DIBootstrap.createInjector(new DefaultBindings(), new JGroupsModule(), module);
+        JavaGroupsBridge bridge = (JavaGroupsBridge) injector.getInstance(EventBridge.class);
+
+        assertEquals(MCAST_ADDRESS_TEST, bridge.getMulticastAddress());
+        assertEquals(MCAST_PORT_TEST, bridge.getMulticastPort());
+        assertEquals(CONFIG_URL_TEST, bridge.getConfigURL());
+    }
+
+    @Test
+    public void testUseDefaultProperties() throws Exception {
+        Injector injector = DIBootstrap.createInjector(new DefaultBindings(), new JGroupsModule());
+        JavaGroupsBridge bridge = (JavaGroupsBridge) injector.getInstance(EventBridge.class);
+
+        assertEquals(JavaGroupsBridge.MCAST_ADDRESS_DEFAULT, bridge.getMulticastAddress());
+        assertEquals(JavaGroupsBridge.MCAST_PORT_DEFAULT, bridge.getMulticastPort());
+        assertEquals(null, bridge.getConfigURL());
+    }
+
+    class DefaultBindings implements Module {
+        @Override
+        public void configure(Binder binder) {
+            binder.bindMap(String.class, Constants.PROPERTIES_MAP);
+            binder.bind(DataDomain.class).toInstance(DOMAIN);
+            binder.bind(EventManager.class).toInstance(EVENT_MANAGER);
+            binder.bind(TransactionManager.class).to(DefaultTransactionManager.class);
+            binder.bind(TransactionFactory.class).to(DefaultTransactionFactory.class);
+            binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class);
+            binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-jms/pom.xml b/cayenne-jms/pom.xml
new file mode 100644
index 0000000..f4da57b
--- /dev/null
+++ b/cayenne-jms/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>cayenne-parent</artifactId>
+        <groupId>org.apache.cayenne</groupId>
+        <version>4.1.M2-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>cayenne-jms</artifactId>
+    <name>cayenne-jms: Cayenne JMS Event bridge</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-jms_1.1_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- This ensures LICENSE and NOTICE inclusion in all jars -->
+            <plugin>
+                <artifactId>maven-remote-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridge.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridge.java b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridge.java
new file mode 100644
index 0000000..0b746fc
--- /dev/null
+++ b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridge.java
@@ -0,0 +1,280 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.util.IDUtil;
+
+import javax.jms.Message;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageListener;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Implementation of EventBridge that passes and receives events via JMS (Java Messaging
+ * Service). JMSBridge uses "publish/subscribe" model for communication with external
+ * agents.
+ * 
+ * @since 1.1
+ */
+public class JMSBridge extends EventBridge implements MessageListener {
+
+    // this is an OpenJMS default for the factory name. Likely it won't work with
+    // anything else
+    public static final String TOPIC_CONNECTION_FACTORY_DEFAULT = "JmsTopicConnectionFactory";
+
+    public static final String TOPIC_CONNECTION_FACTORY_PROPERTY = "cayenne.JMSBridge.topic.connection.factory";
+
+    static final String VM_ID = new String(IDUtil.pseudoUniqueByteSequence16());
+    static final String VM_ID_PROPERTY = "VM_ID";
+
+    protected String topicConnectionFactoryName;
+
+    protected TopicConnection sendConnection;
+    protected TopicSession sendSession;
+    protected TopicConnection receivedConnection;
+    protected TopicPublisher publisher;
+    protected TopicSubscriber subscriber;
+
+    public JMSBridge(EventSubject localSubject, String externalSubject) {
+        super(localSubject, externalSubject);
+    }
+
+    /**
+     * @since 1.2
+     */
+    public JMSBridge(Collection<EventSubject> localSubjects, String externalSubject) {
+        super(localSubjects, externalSubject);
+    }
+
+    /**
+     * @since 4.0
+     */
+    public JMSBridge(Collection<EventSubject> localSubjects, String externalSubject, Map<String, String> properties) {
+        super(localSubjects, externalSubject);
+
+        // configure properties
+        String topicConnectionFactory = properties
+                .get(TOPIC_CONNECTION_FACTORY_PROPERTY);
+
+        this.topicConnectionFactoryName = (topicConnectionFactory != null)
+                ? topicConnectionFactory
+                : TOPIC_CONNECTION_FACTORY_DEFAULT;
+    }
+
+    /**
+     * JMS MessageListener implementation. Injects received events to the EventManager
+     * local event queue.
+     */
+    public void onMessage(Message message) {
+
+        try {
+            Object vmID = message.getObjectProperty(JMSBridge.VM_ID_PROPERTY);
+            if (JMSBridge.VM_ID.equals(vmID)) {
+                return;
+            }
+
+            if (!(message instanceof ObjectMessage)) {
+                return;
+            }
+
+            ObjectMessage objectMessage = (ObjectMessage) message;
+            CayenneEvent event = messageObjectToEvent(objectMessage.getObject());
+            if (event != null) {
+                onExternalEvent(event);
+            }
+
+        }
+        catch (MessageFormatException mfex) {
+            // TODO: Andrus, 2/8/2006 logging... Log4J was removed to make this usable on
+            // the client
+        }
+        catch (Exception ex) {
+            // TODO: Andrus, 2/8/2006 logging... Log4J was removed to make this usable on
+            // the client
+        }
+    }
+
+    /**
+     * @return Name of javax.jms.TopicConnectionFactory accessible via JNDI.
+     */
+    public String getTopicConnectionFactoryName() {
+        return topicConnectionFactoryName;
+    }
+
+    public void setTopicConnectionFactoryName(String name) {
+        this.topicConnectionFactoryName = name;
+    }
+
+    /**
+     * Starts up JMS machinery for "publish/subscribe" model.
+     */
+    @Override
+    protected void startupExternal() throws Exception {
+        Context jndiContext = new InitialContext();
+        TopicConnectionFactory connectionFactory = (TopicConnectionFactory) jndiContext
+                .lookup(topicConnectionFactoryName);
+
+        Topic topic = null;
+
+        try {
+            topic = (Topic) jndiContext.lookup(externalSubject);
+        }
+        catch (NameNotFoundException ex) {
+            // can't find topic, try to create it
+            topic = topicNotFound(jndiContext, ex);
+
+            if (topic == null) {
+                throw ex;
+            }
+        }
+
+        // config publisher
+        if (receivesLocalEvents()) {
+            this.sendConnection = connectionFactory.createTopicConnection();
+            this.sendSession = sendConnection.createTopicSession(
+                    false,
+                    Session.AUTO_ACKNOWLEDGE);
+            this.publisher = sendSession.createPublisher(topic);
+        }
+
+        // config subscriber
+        if (receivesExternalEvents()) {
+            this.receivedConnection = connectionFactory.createTopicConnection();
+            this.subscriber = receivedConnection.createTopicSession(
+                    false,
+                    Session.AUTO_ACKNOWLEDGE).createSubscriber(topic);
+            this.subscriber.setMessageListener(this);
+            this.receivedConnection.start();
+        }
+    }
+
+    /**
+     * Attempts to create missing Topic. Since Topic creation is JMS-implementation
+     * specific, this task is left to subclasses. Current implementation simply rethrows
+     * the exception.
+     */
+    protected Topic topicNotFound(Context jndiContext, NamingException ex)
+            throws Exception {
+        throw ex;
+    }
+
+    /**
+     * Closes all resources used to communicate via JMS.
+     */
+    @Override
+    protected void shutdownExternal() throws Exception {
+        Exception lastException = null;
+
+        if (publisher != null) {
+            try {
+                publisher.close();
+            }
+            catch (Exception ex) {
+                lastException = ex;
+            }
+        }
+
+        if (subscriber != null) {
+            try {
+                subscriber.close();
+            }
+            catch (Exception ex) {
+                lastException = ex;
+            }
+        }
+
+        if (receivedConnection != null) {
+            try {
+                receivedConnection.close();
+            }
+            catch (Exception ex) {
+                lastException = ex;
+            }
+        }
+
+        if (sendSession != null) {
+            try {
+                sendSession.close();
+            }
+            catch (Exception ex) {
+                lastException = ex;
+            }
+        }
+
+        if (sendConnection != null) {
+            try {
+                sendConnection.close();
+            }
+            catch (Exception ex) {
+                lastException = ex;
+            }
+        }
+
+        publisher = null;
+        subscriber = null;
+        receivedConnection = null;
+        sendConnection = null;
+        sendSession = null;
+
+        if (lastException != null) {
+            throw lastException;
+        }
+    }
+
+    @Override
+    protected void sendExternalEvent(CayenneEvent localEvent) throws Exception {
+        ObjectMessage message = sendSession
+                .createObjectMessage(eventToMessageObject(localEvent));
+        message.setObjectProperty(JMSBridge.VM_ID_PROPERTY, JMSBridge.VM_ID);
+        publisher.publish(message);
+    }
+
+    /**
+     * Converts CayenneEvent to a serializable object that will be sent via JMS. Default
+     * implementation simply returns the event, but subclasses can customize this
+     * behavior.
+     */
+    protected Serializable eventToMessageObject(CayenneEvent event) throws Exception {
+        return event;
+    }
+
+    /**
+     * Converts a Serializable instance to CayenneEvent. Returns null if the object is not
+     * supported. Default implementation simply tries to cast the object to CayenneEvent,
+     * but subclasses can customize this behavior.
+     */
+    protected CayenneEvent messageObjectToEvent(Serializable object) throws Exception {
+        return (object instanceof CayenneEvent) ? (CayenneEvent) object : null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeFactory.java b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeFactory.java
new file mode 100644
index 0000000..b7772d8
--- /dev/null
+++ b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeFactory.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Factory to create JMSBridge instances.
+ * 
+ * @since 1.1
+ */
+public class JMSBridgeFactory implements EventBridgeFactory {
+
+    /**
+     * @since 1.2
+     */
+    public EventBridge createEventBridge(Collection<EventSubject> localSubjects, String externalSubject, Map<String, String> properties) {
+        return new JMSBridge(localSubjects, externalSubject, properties);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeProvider.java b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeProvider.java
new file mode 100644
index 0000000..6a06faa
--- /dev/null
+++ b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSBridgeProvider.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.access.DataDomain;
+import org.apache.cayenne.access.DataRowStore;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.di.DIRuntimeException;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Provider;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class JMSBridgeProvider implements Provider<EventBridge> {
+
+    @Inject
+    protected DataDomain dataDomain;
+
+    @Inject(JMSModule.JMS_BRIDGE_PROPERTIES_MAP)
+    Map<String, String> properties;
+
+    @Override
+    public EventBridge get() throws DIRuntimeException {
+        EventSubject snapshotEventSubject = EventSubject.getSubject(DataRowStore.class, dataDomain.getName());
+
+        return new JMSBridge(
+                Collections.singleton(snapshotEventSubject),
+                EventBridge.convertToExternalSubject(snapshotEventSubject),
+                properties);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSModule.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSModule.java b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSModule.java
new file mode 100644
index 0000000..b532bc5
--- /dev/null
+++ b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSModule.java
@@ -0,0 +1,54 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.MapBuilder;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.0
+ */
+public class JMSModule implements Module {
+
+    /**
+     * A DI container key for the Map&lt;String, String&gt; storing
+     * {@link org.apache.cayenne.event.JMSBridge} properties
+     *
+     * @since 4.0
+     */
+    public static final String JMS_BRIDGE_PROPERTIES_MAP = "cayenne.server.jms_bridge";
+
+    public static void contributeTopicConnectionFactory(Binder binder, String factory) {
+        contributeProperties(binder).put(JMSBridge.TOPIC_CONNECTION_FACTORY_PROPERTY, factory);
+    }
+
+    private static MapBuilder<String> contributeProperties(Binder binder) {
+        return binder.bindMap(String.class, JMS_BRIDGE_PROPERTIES_MAP);
+    }
+
+    @Override
+    public void configure(Binder binder) {
+        // init properties' defaults
+        contributeTopicConnectionFactory(binder, JMSBridge.TOPIC_CONNECTION_FACTORY_DEFAULT);
+
+        binder.bind(EventBridge.class).toProvider(JMSBridgeProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSServerModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSServerModuleProvider.java b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSServerModuleProvider.java
new file mode 100644
index 0000000..6979d21
--- /dev/null
+++ b/cayenne-jms/src/main/java/org/apache/cayenne/event/JMSServerModuleProvider.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.configuration.server.CayenneServerModuleProvider;
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.0
+ */
+public class JMSServerModuleProvider implements CayenneServerModuleProvider {
+
+    @Override
+    public Module module() {
+        return new JMSModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return JMSModule.class;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Collection<Class<? extends Module>> overrides() {
+        Collection modules = Collections.singletonList(ServerModule.class);
+        return modules;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider b/cayenne-jms/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
new file mode 100644
index 0000000..7c54e3f
--- /dev/null
+++ b/cayenne-jms/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
@@ -0,0 +1,20 @@
+##################################################################
+#   Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you 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.
+##################################################################
+
+org.apache.cayenne.event.JMSServerModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/test/java/org/apache/cayenne/event/CayenneJMSModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/test/java/org/apache/cayenne/event/CayenneJMSModuleProviderTest.java b/cayenne-jms/src/test/java/org/apache/cayenne/event/CayenneJMSModuleProviderTest.java
new file mode 100644
index 0000000..3b3e87a
--- /dev/null
+++ b/cayenne-jms/src/test/java/org/apache/cayenne/event/CayenneJMSModuleProviderTest.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.configuration.server.CayenneServerModuleProvider;
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+/**
+ * @since 4.0
+ */
+public class CayenneJMSModuleProviderTest {
+
+    @Test
+    public void testAutoLoadable() {
+        ModuleProviderChecker.testProviderPresent(JMSServerModuleProvider.class, CayenneServerModuleProvider.class);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeFactoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeFactoryTest.java b/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeFactoryTest.java
new file mode 100644
index 0000000..1e50145
--- /dev/null
+++ b/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeFactoryTest.java
@@ -0,0 +1,77 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class JMSBridgeFactoryTest {
+
+    protected Collection<EventSubject> subjects = Collections.singleton(new EventSubject("test"));
+    protected String externalSubject = "subject";
+
+    @Test
+    public void testCreateEventBridge() throws Exception {
+        EventBridge bridge = new JMSBridgeFactory().createEventBridge(
+                subjects,
+                externalSubject,
+                Collections.<String, String>emptyMap());
+
+        assertNotNull(bridge);
+        assertTrue(bridge instanceof JMSBridge);
+        assertEquals(subjects, bridge.getLocalSubjects());
+        assertEquals(externalSubject, bridge.getExternalSubject());
+    }
+
+    @Test
+    public void testUseProperties() throws Exception {
+        JMSBridgeFactory bridgeFactory = new JMSBridgeFactory();
+
+        Map<String, String> properties = new HashMap<>();
+        properties.put(JMSBridge.TOPIC_CONNECTION_FACTORY_PROPERTY, JMSBridgeProviderTest.TOPIC_CONNECTION_FACTORY_TEST);
+
+        JMSBridge bridge = (JMSBridge) bridgeFactory.createEventBridge(
+                subjects,
+                externalSubject,
+                properties);
+
+        assertEquals(bridge.getTopicConnectionFactoryName(), JMSBridgeProviderTest.TOPIC_CONNECTION_FACTORY_TEST);
+    }
+
+    @Test
+    public void testUseDefaultProperties() throws Exception {
+        JMSBridgeFactory bridgeFactory = new JMSBridgeFactory();
+        JMSBridge bridge = (JMSBridge) bridgeFactory.createEventBridge(
+                subjects,
+                externalSubject,
+                Collections.<String, String>emptyMap());
+
+        assertEquals(bridge.getTopicConnectionFactoryName(), JMSBridge.TOPIC_CONNECTION_FACTORY_DEFAULT);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeProviderTest.java b/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeProviderTest.java
new file mode 100644
index 0000000..83404eb
--- /dev/null
+++ b/cayenne-jms/src/test/java/org/apache/cayenne/event/JMSBridgeProviderTest.java
@@ -0,0 +1,87 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.cayenne.event;
+
+import org.apache.cayenne.access.DataDomain;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.log.Slf4jJdbcEventLogger;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.tx.DefaultTransactionFactory;
+import org.apache.cayenne.tx.DefaultTransactionManager;
+import org.apache.cayenne.tx.TransactionFactory;
+import org.apache.cayenne.tx.TransactionManager;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class JMSBridgeProviderTest {
+
+    private final DataDomain DOMAIN = new DataDomain("test");
+    private final EventManager EVENT_MANAGER = new DefaultEventManager();
+    protected static final String TOPIC_CONNECTION_FACTORY_TEST = "SomeTopicConnectionFactory";
+
+    @Test
+    public void testGetJMSBridge() throws Exception {
+        Injector injector = DIBootstrap.createInjector(new DefaultBindings(), new JMSModule());
+        EventBridge bridge = injector.getInstance(EventBridge.class);
+
+        assertNotNull(bridge);
+        assertTrue(bridge instanceof JMSBridge);
+    }
+
+    @Test
+    public void testUseProperties() {
+        Module module = binder -> JMSModule.contributeTopicConnectionFactory(binder, TOPIC_CONNECTION_FACTORY_TEST);
+
+        Injector injector = DIBootstrap.createInjector(new DefaultBindings(), new JMSModule(), module);
+        JMSBridge bridge = (JMSBridge) injector.getInstance(EventBridge.class);
+
+        assertEquals(TOPIC_CONNECTION_FACTORY_TEST, bridge.getTopicConnectionFactoryName());
+    }
+
+    @Test
+    public void testUseDefaultProperties() throws Exception {
+        Injector injector = DIBootstrap.createInjector(new DefaultBindings(), new JMSModule());
+        JMSBridge bridge = (JMSBridge) injector.getInstance(EventBridge.class);
+
+        assertEquals(JMSBridge.TOPIC_CONNECTION_FACTORY_DEFAULT, bridge.getTopicConnectionFactoryName());
+    }
+
+    class DefaultBindings implements Module {
+        @Override
+        public void configure(Binder binder) {
+            binder.bindMap(String.class, Constants.PROPERTIES_MAP);
+            binder.bind(DataDomain.class).toInstance(DOMAIN);
+            binder.bind(EventManager.class).toInstance(EVENT_MANAGER);
+            binder.bind(TransactionManager.class).to(DefaultTransactionManager.class);
+            binder.bind(TransactionFactory.class).to(DefaultTransactionFactory.class);
+            binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class);
+            binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ab1fd0bf/cayenne-xmpp/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-xmpp/pom.xml b/cayenne-xmpp/pom.xml
new file mode 100644
index 0000000..cf3f849
--- /dev/null
+++ b/cayenne-xmpp/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>cayenne-parent</artifactId>
+        <groupId>org.apache.cayenne</groupId>
+        <version>4.1.M2-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>cayenne-xmpp</artifactId>
+    <name>cayenne-xmpp: Cayenne XMPP Event bridge</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>jivesoftware</groupId>
+            <artifactId>smack</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>jivesoftware</groupId>
+            <artifactId>smackx</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- This ensures LICENSE and NOTICE inclusion in all jars -->
+            <plugin>
+                <artifactId>maven-remote-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file