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 2017/12/12 13:05:43 UTC

[1/5] cayenne git commit: CAY-2373 cayenne-rop-server module - move org.apache.cayenne.remote package to cayenne-rop server module - remove dependencies from cayenne-server pom.xml - update tutorial

Repository: cayenne
Updated Branches:
  refs/heads/master eec08b7ef -> 38f37d79a


http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java
deleted file mode 100644
index 00feca6..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ObjectContextFactory;
-import org.apache.cayenne.event.MockEventBridgeFactory;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.remote.QueryMessage;
-import org.apache.cayenne.remote.RemoteSession;
-import org.apache.cayenne.util.Util;
-import org.junit.Test;
-
-public class BaseRemoteServiceTest {
-
-	@Test
-	public void testConstructor() throws Exception {
-
-		Map<String, String> map = new HashMap<>();
-		map.put(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY, MockEventBridgeFactory.class.getName());
-
-		ObjectContextFactory factory = new ObjectContextFactory() {
-
-			public ObjectContext createContext(DataChannel parent) {
-				return null;
-			}
-
-			public ObjectContext createContext() {
-				return null;
-			}
-		};
-		BaseRemoteService service = new BaseRemoteService(factory, map) {
-
-			@Override
-			protected ServerSession createServerSession() {
-				return null;
-			}
-
-			@Override
-			protected ServerSession createServerSession(String name) {
-				return null;
-			}
-
-			@Override
-			protected ServerSession getServerSession() {
-				return null;
-			}
-		};
-		assertEquals(MockEventBridgeFactory.class.getName(), service.getEventBridgeFactoryName());
-		assertSame(factory, service.contextFactory);
-
-	}
-
-	@Test
-	public void testProcessMessageExceptionSerializability() throws Throwable {
-
-		Map<String, String> map = new HashMap<>();
-		ObjectContextFactory factory = new ObjectContextFactory() {
-
-			public ObjectContext createContext(DataChannel parent) {
-				return null;
-			}
-
-			public ObjectContext createContext() {
-				return null;
-			}
-		};
-		BaseRemoteService service = new BaseRemoteService(factory, map) {
-
-			@Override
-			protected ServerSession createServerSession() {
-				return new ServerSession(new RemoteSession("a"), null);
-			}
-
-			@Override
-			protected ServerSession createServerSession(String name) {
-				return createServerSession();
-			}
-
-			@Override
-			protected ServerSession getServerSession() {
-				return createServerSession();
-			}
-		};
-
-		try {
-			service.processMessage(new QueryMessage(null) {
-
-				@Override
-				public Query getQuery() {
-					// serializable exception thrown
-					throw new CayenneRuntimeException();
-				}
-			});
-
-			fail("Expected to throw");
-		} catch (Exception ex) {
-			Util.cloneViaSerialization(ex);
-		}
-
-		try {
-			service.processMessage(new QueryMessage(null) {
-
-				@Override
-				public Query getQuery() {
-					// non-serializable exception thrown
-					throw new MockUnserializableException();
-				}
-			});
-
-			fail("Expected to throw");
-		} catch (Exception ex) {
-			Util.cloneViaSerialization(ex);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java
deleted file mode 100644
index e4b01fe..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.MockDataChannel;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.remote.BootstrapMessage;
-import org.apache.cayenne.remote.ClientMessage;
-import org.junit.Test;
-
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-
-public class DispatchHelperTest {
-
-    @Test
-    public void testBootstrapMessage() {
-        EntityResolver resolver = new EntityResolver();
-        MockDataChannel channel = new MockDataChannel(resolver);
-        assertSame(resolver.getClientEntityResolver(), DispatchHelper.dispatch(
-                channel,
-                new BootstrapMessage()));
-    }
-
-    @Test
-    public void testUnknownMessage() {
-        try {
-            DispatchHelper.dispatch(new MockDataChannel(), mock(ClientMessage.class));
-            fail("Unknown message must have failed");
-        }
-        catch (CayenneRuntimeException e) {
-            // expected
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java
deleted file mode 100644
index e30435b..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-class MockUnserializableException extends RuntimeException {
-
-    protected Object notSerializableField = new Object();
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/tutorials/tutorial-rop-client/src/main/java/org/apache/cayenne/tutorial/persistent/client/Main.java
----------------------------------------------------------------------
diff --git a/tutorials/tutorial-rop-client/src/main/java/org/apache/cayenne/tutorial/persistent/client/Main.java b/tutorials/tutorial-rop-client/src/main/java/org/apache/cayenne/tutorial/persistent/client/Main.java
index e2434ce..d70843c 100644
--- a/tutorials/tutorial-rop-client/src/main/java/org/apache/cayenne/tutorial/persistent/client/Main.java
+++ b/tutorials/tutorial-rop-client/src/main/java/org/apache/cayenne/tutorial/persistent/client/Main.java
@@ -32,7 +32,7 @@ public class Main {
     public static void main(String[] args) {
 
         Map<String, String> properties = new HashMap<>();
-        properties.put(ClientConstants.ROP_SERVICE_URL_PROPERTY, "http://localhost:8080/tutorial-rop-server/cayenne-service");
+        properties.put(ClientConstants.ROP_SERVICE_URL_PROPERTY, "http://localhost:8080/cayenne-service");
         properties.put(ClientConstants.ROP_SERVICE_USERNAME_PROPERTY, "cayenne-user");
         properties.put(ClientConstants.ROP_SERVICE_PASSWORD_PROPERTY, "secret");
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/tutorials/tutorial-rop-server/pom.xml
----------------------------------------------------------------------
diff --git a/tutorials/tutorial-rop-server/pom.xml b/tutorials/tutorial-rop-server/pom.xml
index c234bd2..4bde8ec 100644
--- a/tutorials/tutorial-rop-server/pom.xml
+++ b/tutorials/tutorial-rop-server/pom.xml
@@ -28,12 +28,13 @@
 	<dependencies>
 		<dependency>
 			<groupId>org.apache.cayenne</groupId>
-			<artifactId>cayenne-server</artifactId>
+			<artifactId>cayenne-rop-server</artifactId>
 			<version>${project.version}</version>
 		</dependency>
 		<dependency>
 			<groupId>javax.servlet</groupId>
 			<artifactId>servlet-api</artifactId>
+			<scope>compile</scope>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.derby</groupId>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/tutorials/tutorial-rop-server/src/main/resources/cayenne-project.xml
----------------------------------------------------------------------
diff --git a/tutorials/tutorial-rop-server/src/main/resources/cayenne-project.xml b/tutorials/tutorial-rop-server/src/main/resources/cayenne-project.xml
index 1c7f367..27b1c1b 100644
--- a/tutorials/tutorial-rop-server/src/main/resources/cayenne-project.xml
+++ b/tutorials/tutorial-rop-server/src/main/resources/cayenne-project.xml
@@ -1,11 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<domain project-version="9">
+<domain xmlns="http://cayenne.apache.org/schema/10/domain"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain http://cayenne.apache.org/schema/10/domain.xsd"
+	 project-version="10">
 	<map name="datamap"/>
-
 	<node name="datanode"
 		 factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory"
-		 schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy"
-		>
+		 schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy">
 		<map-ref name="datamap"/>
 		<data-source>
 			<driver value="org.apache.derby.jdbc.EmbeddedDriver"/>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/tutorials/tutorial-rop-server/src/main/resources/datamap.map.xml
----------------------------------------------------------------------
diff --git a/tutorials/tutorial-rop-server/src/main/resources/datamap.map.xml b/tutorials/tutorial-rop-server/src/main/resources/datamap.map.xml
index e0a7e3f..f879288 100644
--- a/tutorials/tutorial-rop-server/src/main/resources/datamap.map.xml
+++ b/tutorials/tutorial-rop-server/src/main/resources/datamap.map.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
-<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap"
+<data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
 	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap http://cayenne.apache.org/schema/9/modelMap.xsd"
-	 project-version="9">
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap http://cayenne.apache.org/schema/10/modelMap.xsd"
+	 project-version="10">
 	<property name="defaultPackage" value="org.apache.cayenne.tutorial.persistent"/>
 	<property name="clientSupported" value="true"/>
 	<property name="defaultClientPackage" value="org.apache.cayenne.tutorial.persistent.client"/>
@@ -37,10 +37,10 @@
 	<db-relationship name="paintings" source="GALLERY" target="PAINTING" toMany="true">
 		<db-attribute-pair source="ID" target="GALLERY_ID"/>
 	</db-relationship>
-	<db-relationship name="artist" source="PAINTING" target="ARTIST" toMany="false">
+	<db-relationship name="artist" source="PAINTING" target="ARTIST">
 		<db-attribute-pair source="ARTIST_ID" target="ID"/>
 	</db-relationship>
-	<db-relationship name="gallery" source="PAINTING" target="GALLERY" toMany="false">
+	<db-relationship name="gallery" source="PAINTING" target="GALLERY">
 		<db-attribute-pair source="GALLERY_ID" target="ID"/>
 	</db-relationship>
 	<obj-relationship name="paintings" source="Artist" target="Painting" deleteRule="Cascade" db-relationship-path="paintings"/>


[4/5] cayenne git commit: CAY-2373 cayenne-rop-server module - move org.apache.cayenne.remote package to cayenne-rop server module - remove dependencies from cayenne-server pom.xml - update tutorial

Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java
new file mode 100644
index 0000000..7ed1004
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java
@@ -0,0 +1,668 @@
+/*****************************************************************
+ *   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.remote;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.QueryResponse;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.util.IDUtil;
+import org.apache.cayenne.util.IncrementalListResponse;
+import org.apache.cayenne.util.Util;
+
+/**
+ * A list that serves as a container of Persistent objects. It is usually returned by an
+ * ObjectContext when a paginated query is performed. Initially only the first "page" of
+ * objects is fully resolved. Pages following the first page are resolved on demand. When
+ * a list element is accessed, the list would ensure that this element as well as all its
+ * siblings on the same page are fully resolved.
+ * <p>
+ * The list can hold DataRows or Persistent objects. Attempts to add any other object
+ * types will result in an exception.
+ * </p>
+ * <p>
+ * Certain operations like <code>toArray</code> would trigger full list fetch.
+ * </p>
+ * <p>
+ * Synchronization Note: this list is not synchronized. All access to it should follow
+ * synchronization rules applicable for ArrayList.
+ * </p>
+ * 
+ * @since 1.2
+ */
+public class RemoteIncrementalFaultList implements List {
+
+    static final Object PLACEHOLDER = new Object();
+
+    protected List elements;
+
+    protected String cacheKey;
+    protected int pageSize;
+    protected int unfetchedObjects;
+    protected Query paginatedQuery;
+
+    protected transient ObjectContext context;
+
+    /**
+     * Stores a hint allowing to distinguish data rows from unfetched ids when the query
+     * fetches data rows.
+     */
+    protected int rowWidth;
+
+    private ListHelper helper;
+
+    public RemoteIncrementalFaultList(ObjectContext context, Query paginatedQuery) {
+
+        QueryMetadata metadata = paginatedQuery.getMetaData(context.getEntityResolver());
+
+        if (metadata.getPageSize() <= 0) {
+            throw new IllegalArgumentException("Page size must be positive: "
+                    + metadata.getPageSize());
+        }
+
+        this.pageSize = metadata.getPageSize();
+        this.helper = (metadata.isFetchingDataRows())
+                ? new DataRowListHelper()
+                : new PersistentListHelper();
+        this.context = context;
+
+        // use provided cache key if possible; this would allow clients to
+        // address the same server-side list from multiple queries.
+        this.cacheKey = metadata.getCacheKey();
+        if (cacheKey == null) {
+            cacheKey = generateCacheKey();
+        }
+
+        Query query = paginatedQuery;
+
+        // always wrap a query in a Incremental*Query, to ensure cache key is
+        // client-generated (e.g. see CAY-1003 - client and server can be in different
+        // timezones, so the key can be messed up)
+
+        // there are some serious pagination optimizations for SelectQuery on the
+        // server-side, so use a special wrapper that is itself a subclass of
+        // SelectQuery
+        if (query instanceof SelectQuery) {
+            query = new IncrementalSelectQuery<Object>((SelectQuery<Object>) paginatedQuery, cacheKey);
+        }
+        else {
+            query = new IncrementalQuery(paginatedQuery, cacheKey);
+        }
+
+        // ensure that originating query is wrapped to include the right cache key....
+        this.paginatedQuery = query;
+
+        // select directly from the channel, bypassing the context. Otherwise our query
+        // wrapper can be intercepted incorrectly
+        QueryResponse response = context.getChannel().onQuery(context, query);
+
+        List firstPage = response.firstList();
+
+        // sanity check
+        if (firstPage.size() > pageSize) {
+            throw new IllegalArgumentException("Returned page size ("
+                    + firstPage.size()
+                    + ") exceeds requested page size ("
+                    + pageSize
+                    + ")");
+        }
+        // result is smaller than a page
+        else if (firstPage.size() < pageSize) {
+            this.elements = new ArrayList(firstPage);
+            unfetchedObjects = 0;
+        }
+        else {
+
+            if (response instanceof IncrementalListResponse) {
+                int fullListSize = ((IncrementalListResponse) response).getFullSize();
+
+                this.unfetchedObjects = fullListSize - firstPage.size();
+                this.elements = new ArrayList(fullListSize);
+                elements.addAll(firstPage);
+
+                // fill the rest with placeholder...
+                for (int i = pageSize; i < fullListSize; i++) {
+                    elements.add(PLACEHOLDER);
+                }
+            }
+            // this happens when full size equals page size
+            else {
+                this.elements = new ArrayList(firstPage);
+                unfetchedObjects = 0;
+            }
+        }
+    }
+
+    private String generateCacheKey() {
+        byte[] bytes = IDUtil.pseudoUniqueByteSequence8();
+        StringBuffer buffer = new StringBuffer(17);
+        buffer.append("I");
+        for (byte aByte : bytes) {
+            IDUtil.appendFormattedByte(buffer, aByte);
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Will resolve all unread objects.
+     */
+    public void resolveAll() {
+        resolveInterval(0, size());
+    }
+
+    /**
+     * @param object
+     * @return <code>true</code> if the object corresponds to an unresolved state and
+     *         does require a fetch before being returned to the user.
+     */
+    private boolean isUnresolved(Object object) {
+        return object == PLACEHOLDER;
+    }
+
+    /**
+     * Resolves a sublist of objects starting at <code>fromIndex</code> up to but not
+     * including <code>toIndex</code>. Internally performs bound checking and trims
+     * indexes accordingly.
+     */
+    protected void resolveInterval(int fromIndex, int toIndex) {
+        if (fromIndex >= toIndex || elements.isEmpty()) {
+            return;
+        }
+
+        if (context == null) {
+            throw new CayenneRuntimeException("No ObjectContext set, can't resolve objects.");
+        }
+
+        // bounds checking
+
+        if (fromIndex < 0) {
+            fromIndex = 0;
+        }
+
+        if (toIndex > elements.size()) {
+            toIndex = elements.size();
+        }
+
+        // find disjoint ranges and resolve them individually...
+
+        int fromPage = pageIndex(fromIndex);
+        int toPage = pageIndex(toIndex - 1);
+
+        int rangeStartIndex = -1;
+        for (int i = fromPage; i <= toPage; i++) {
+
+            int pageStartIndex = i * pageSize;
+            Object firstPageObject = elements.get(pageStartIndex);
+            if (isUnresolved(firstPageObject)) {
+
+                // start range
+                if (rangeStartIndex < 0) {
+                    rangeStartIndex = pageStartIndex;
+                }
+            }
+            else {
+
+                // finish range...
+                if (rangeStartIndex >= 0) {
+                    forceResolveInterval(rangeStartIndex, pageStartIndex);
+                    rangeStartIndex = -1;
+                }
+            }
+        }
+
+        // load last page
+        if (rangeStartIndex >= 0) {
+            forceResolveInterval(rangeStartIndex, toIndex);
+        }
+    }
+
+    void forceResolveInterval(int fromIndex, int toIndex) {
+
+        int pastEnd = toIndex - size();
+        if (pastEnd > 0) {
+            toIndex = size();
+        }
+
+        int fetchLimit = toIndex - fromIndex;
+
+        RangeQuery query = new RangeQuery(cacheKey, fromIndex, fetchLimit, paginatedQuery);
+
+        List sublist = context.performQuery(query);
+
+        // sanity check
+        if (sublist.size() != fetchLimit) {
+            throw new CayenneRuntimeException("Resolved range size %d is not the same as expected: %d"
+                    , sublist.size(), fetchLimit);
+        }
+
+        for (int i = 0; i < fetchLimit; i++) {
+            elements.set(fromIndex + i, sublist.get(i));
+        }
+
+        unfetchedObjects -= sublist.size();
+    }
+
+    /**
+     * Returns zero-based index of the virtual "page" for a given array element index.
+     */
+    int pageIndex(int elementIndex) {
+        if (elementIndex < 0 || elementIndex > size()) {
+            throw new IndexOutOfBoundsException("Index: " + elementIndex);
+        }
+
+        if (pageSize <= 0 || elementIndex < 0) {
+            return -1;
+        }
+
+        return elementIndex / pageSize;
+    }
+
+    /**
+     * Returns ObjectContext associated with this list.
+     */
+    public ObjectContext getContext() {
+        return context;
+    }
+
+    /**
+     * Returns the pageSize.
+     * 
+     * @return int
+     */
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    /**
+     * Returns a list iterator for this list. DataObjects are resolved a page (according
+     * to getPageSize()) at a time as necessary - when retrieved with next() or
+     * previous().
+     */
+    public ListIterator listIterator() {
+        return new ListIteratorHelper(0);
+    }
+
+    /**
+     * Returns a list iterator of the elements in this list (in proper sequence), starting
+     * at the specified position in this list. The specified index indicates the first
+     * element that would be returned by an initial call to the next method. An initial
+     * call to the previous method would return the element with the specified index minus
+     * one. DataObjects are resolved a page at a time (according to getPageSize()) as
+     * necessary - when retrieved with next() or previous().
+     */
+    public ListIterator listIterator(int index) {
+        if (index < 0 || index > size()) {
+            throw new IndexOutOfBoundsException("Index: " + index);
+        }
+
+        return new ListIteratorHelper(index);
+    }
+
+    /**
+     * Return an iterator for this list. DataObjects are resolved a page (according to
+     * getPageSize()) at a time as necessary - when retrieved with next().
+     */
+    public Iterator iterator() {
+        // by virtue of get(index)'s implementation, resolution of ids into
+        // objects will occur on pageSize boundaries as necessary.
+        return new Iterator() {
+
+            int listIndex = 0;
+
+            public boolean hasNext() {
+                return (listIndex < elements.size());
+            }
+
+            public Object next() {
+                if (listIndex >= elements.size())
+                    throw new NoSuchElementException("no more elements");
+
+                return get(listIndex++);
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException("remove not supported.");
+            }
+        };
+    }
+
+    /**
+     * @see java.util.List#add(int, Object)
+     */
+    public void add(int index, Object element) {
+        helper.validateListObject(element);
+        elements.add(index, element);
+
+    }
+
+    /**
+     * @see java.util.Collection#add(Object)
+     */
+    public boolean add(Object o) {
+        helper.validateListObject(o);
+        return elements.add(o);
+    }
+
+    /**
+     * @see java.util.Collection#addAll(Collection)
+     */
+    public boolean addAll(Collection c) {
+
+        return elements.addAll(c);
+
+    }
+
+    /**
+     * @see java.util.List#addAll(int, Collection)
+     */
+    public boolean addAll(int index, Collection c) {
+
+        return elements.addAll(index, c);
+
+    }
+
+    /**
+     * @see java.util.Collection#clear()
+     */
+    public void clear() {
+        elements.clear();
+    }
+
+    /**
+     * @see java.util.Collection#contains(Object)
+     */
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+
+    /**
+     * @see java.util.Collection#containsAll(Collection)
+     */
+    public boolean containsAll(Collection c) {
+        Iterator it = c.iterator();
+        while (it.hasNext()) {
+            if (!contains(it.next())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @see java.util.List#get(int)
+     */
+    public Object get(int index) {
+
+        Object o = elements.get(index);
+
+        if (isUnresolved(o)) {
+            // read this page
+            int pageStart = pageIndex(index) * pageSize;
+            resolveInterval(pageStart, pageStart + pageSize);
+
+            return elements.get(index);
+        }
+        else {
+            return o;
+        }
+
+    }
+
+    /**
+     * @see java.util.List#indexOf(Object)
+     */
+    public int indexOf(Object o) {
+        return helper.indexOfObject(o);
+    }
+
+    /**
+     * @see java.util.Collection#isEmpty()
+     */
+    public boolean isEmpty() {
+
+        return elements.isEmpty();
+
+    }
+
+    /**
+     * @see java.util.List#lastIndexOf(Object)
+     */
+    public int lastIndexOf(Object o) {
+        return helper.lastIndexOfObject(o);
+    }
+
+    /**
+     * @see java.util.List#remove(int)
+     */
+    public Object remove(int index) {
+
+        return elements.remove(index);
+
+    }
+
+    /**
+     * @see java.util.Collection#remove(Object)
+     */
+    public boolean remove(Object o) {
+
+        return elements.remove(o);
+
+    }
+
+    /**
+     * @see java.util.Collection#removeAll(Collection)
+     */
+    public boolean removeAll(Collection c) {
+
+        return elements.removeAll(c);
+
+    }
+
+    /**
+     * @see java.util.Collection#retainAll(Collection)
+     */
+    public boolean retainAll(Collection c) {
+
+        return elements.retainAll(c);
+
+    }
+
+    /**
+     * @see java.util.List#set(int, Object)
+     */
+    public Object set(int index, Object element) {
+        helper.validateListObject(element);
+
+        return elements.set(index, element);
+
+    }
+
+    /**
+     * @see java.util.Collection#size()
+     */
+    public int size() {
+        return elements.size();
+    }
+
+    public List subList(int fromIndex, int toIndex) {
+        resolveInterval(fromIndex, toIndex);
+        return elements.subList(fromIndex, toIndex);
+    }
+
+    public Object[] toArray() {
+        resolveAll();
+
+        return elements.toArray();
+    }
+
+    /**
+     * @see java.util.Collection#toArray(Object[])
+     */
+    public Object[] toArray(Object[] a) {
+        resolveAll();
+
+        return elements.toArray(a);
+    }
+
+    /**
+     * Returns a total number of objects that are not resolved yet.
+     */
+    public int getUnfetchedObjects() {
+        return unfetchedObjects;
+    }
+
+    abstract class ListHelper {
+
+        int indexOfObject(Object object) {
+            if (incorrectObjectType(object)) {
+                return -1;
+            }
+
+            for (int i = 0; i < elements.size(); i++) {
+
+                if (Util.nullSafeEquals(object, get(i))) {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+
+        int lastIndexOfObject(Object object) {
+            if (incorrectObjectType(object)) {
+                return -1;
+            }
+
+            for (int i = elements.size() - 1; i >= 0; i--) {
+                if (Util.nullSafeEquals(object, get(i))) {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+
+        abstract boolean incorrectObjectType(Object object);
+
+        void validateListObject(Object object) throws IllegalArgumentException {
+            if (incorrectObjectType(object)) {
+                throw new IllegalArgumentException("Can't store this object: " + object);
+            }
+        }
+    }
+
+    class PersistentListHelper extends ListHelper {
+
+        @Override
+        boolean incorrectObjectType(Object object) {
+            if (!(object instanceof Persistent)) {
+                return true;
+            }
+
+            Persistent persistent = (Persistent) object;
+            if (persistent.getObjectContext() != context) {
+                return true;
+            }
+
+            return false;
+        }
+
+    }
+
+    class DataRowListHelper extends ListHelper {
+
+        @Override
+        boolean incorrectObjectType(Object object) {
+            if (!(object instanceof Map)) {
+                return true;
+            }
+
+            Map map = (Map) object;
+            return map.size() != rowWidth;
+        }
+    }
+
+    class ListIteratorHelper implements ListIterator {
+
+        // by virtue of get(index)'s implementation, resolution of ids into
+        // objects will occur on pageSize boundaries as necessary.
+
+        int listIndex;
+
+        public ListIteratorHelper(int startIndex) {
+            this.listIndex = startIndex;
+        }
+
+        public void add(Object o) {
+            throw new UnsupportedOperationException("add operation not supported");
+        }
+
+        public boolean hasNext() {
+            return (listIndex < elements.size());
+        }
+
+        public boolean hasPrevious() {
+            return (listIndex > 0);
+        }
+
+        public Object next() {
+            if (listIndex >= elements.size())
+                throw new NoSuchElementException("at the end of the list");
+
+            return get(listIndex++);
+        }
+
+        public int nextIndex() {
+            return listIndex;
+        }
+
+        public Object previous() {
+            if (listIndex < 1)
+                throw new NoSuchElementException("at the beginning of the list");
+
+            return get(--listIndex);
+        }
+
+        public int previousIndex() {
+            return (listIndex - 1);
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException("remove operation not supported");
+        }
+
+        public void set(Object o) {
+            throw new UnsupportedOperationException("set operation not supported");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteService.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteService.java
new file mode 100644
index 0000000..f357846
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteService.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.remote;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Interface of a Cayenne remote service.
+ *
+ * @since 1.2
+ * @see org.apache.cayenne.rop.ROPServlet
+ */
+public interface RemoteService extends Remote {
+
+    /**
+     * Establishes a dedicated session with Cayenne DataChannel, returning session id.
+     */
+    RemoteSession establishSession() throws RemoteException;
+
+    /**
+     * Creates a new session with the specified or joins an existing one. This method is
+     * used to bootstrap collaborating clients of a single "group chat".
+     */
+    RemoteSession establishSharedSession(String name) throws RemoteException;
+
+    /**
+     * Processes message on a remote server, returning the result of such processing.
+     */
+    Object processMessage(ClientMessage message) throws RemoteException, Throwable;
+
+    /**
+     * Close remote service resources.
+     * @sine 4.0
+     */
+    void close() throws RemoteException;
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java
new file mode 100644
index 0000000..461e79d
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java
@@ -0,0 +1,145 @@
+/*****************************************************************
+ *   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.remote;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.event.EventBridge;
+import org.apache.cayenne.event.EventBridgeFactory;
+import org.apache.cayenne.event.EventSubject;
+import org.apache.cayenne.util.HashCodeBuilder;
+import org.apache.cayenne.util.ToStringBuilder;
+
+/**
+ * A descriptor used by default service implementation to pass session parameters to the
+ * client. It provides the client with details on how to invoke the service and how to
+ * listen for the server events.
+ * 
+ * @since 1.2
+ */
+public class RemoteSession implements Serializable {
+
+    static final Collection<EventSubject> SUBJECTS = Arrays.asList(
+            DataChannel.GRAPH_CHANGED_SUBJECT,
+            DataChannel.GRAPH_FLUSHED_SUBJECT,
+            DataChannel.GRAPH_ROLLEDBACK_SUBJECT);
+
+    protected String name;
+    protected String sessionId;
+
+    protected String eventBridgeFactory;
+    protected Map<String, String> eventBridgeParameters;
+
+    // private constructor used by hessian deserialization mechanism
+    @SuppressWarnings("unused")
+    private RemoteSession() {
+
+    }
+
+    /**
+     * Creates a HessianServiceDescriptor without server events support.
+     */
+    public RemoteSession(String sessionId) {
+        this(sessionId, null, null);
+    }
+
+    /**
+     * Creates a HessianServiceDescriptor. If <code>eventBridgeFactory</code> argument
+     * is not null, session will support server events.
+     */
+    public RemoteSession(String sessionId, String eventBridgeFactory,
+            Map<String, String> eventBridgeParameters) {
+
+        if (sessionId == null) {
+            throw new IllegalArgumentException("Null sessionId");
+        }
+
+        this.sessionId = sessionId;
+        this.eventBridgeFactory = eventBridgeFactory;
+        this.eventBridgeParameters = eventBridgeParameters;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(71, 5).append(sessionId).toHashCode();
+    }
+
+    /**
+     * Returns server session id. This is often the same as HttpSession id.
+     */
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    /**
+     * Returns session group name. Group name is used for shared sessions.
+     */
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean isServerEventsEnabled() {
+        return eventBridgeFactory != null;
+    }
+
+    @Override
+    public String toString() {
+        ToStringBuilder builder = new ToStringBuilder(this)
+                .append("sessionId", sessionId);
+
+        if (eventBridgeFactory != null) {
+            builder.append("eventBridgeFactory", eventBridgeFactory);
+        }
+
+        if (name != null) {
+            builder.append("name", name);
+        }
+
+        return builder.toString();
+    }
+
+    public static Collection<EventSubject> getSubjects() {
+        return SUBJECTS;
+    }
+
+    /**
+     * @since 4.0
+     */
+    public String getEventBridgeFactory() {
+        return eventBridgeFactory;
+    }
+
+    /**
+     * @since 4.0
+     */
+    public Map<String, String> getEventBridgeParameters() {
+        return eventBridgeParameters != null ? eventBridgeParameters : Collections.<String, String> emptyMap();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java
new file mode 100644
index 0000000..d8d5f37
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java
@@ -0,0 +1,91 @@
+/*****************************************************************
+ *   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.remote;
+
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.graph.GraphDiff;
+
+/**
+ * A message used for synchronization of the child with parent. It defines a few types of
+ * synchronization: "flush" (when the child sends its changes without a commit), "commit"
+ * (cascading flush with ultimate commit to the database), and "rollback" - cascading
+ * reverting of all uncommitted changes.
+ * 
+ * @since 1.2
+ */
+public class SyncMessage implements ClientMessage {
+
+    protected transient ObjectContext source;
+    protected int type;
+    protected GraphDiff senderChanges;
+
+    // private constructor for Hessian deserialization
+    @SuppressWarnings("unused")
+    private SyncMessage() {
+
+    }
+
+    public SyncMessage(ObjectContext source, int syncType, GraphDiff senderChanges) {
+        // validate type
+        if (syncType != DataChannel.FLUSH_NOCASCADE_SYNC
+                && syncType != DataChannel.FLUSH_CASCADE_SYNC
+                && syncType != DataChannel.ROLLBACK_CASCADE_SYNC) {
+            throw new IllegalArgumentException("'type' is invalid: " + syncType);
+        }
+
+        this.source = source;
+        this.type = syncType;
+        this.senderChanges = senderChanges;
+    }
+
+    /**
+     * Returns a source of SyncMessage.
+     */
+    public ObjectContext getSource() {
+        return source;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public GraphDiff getSenderChanges() {
+        return senderChanges;
+    }
+
+    /**
+     * Returns a description of the type of message.
+     * Possibilities are "flush-sync", "flush-cascade-sync", "rollback-cascade-sync" or "unknown-sync".
+     */
+    @Override
+    public String toString() {
+        switch (type) {
+            case DataChannel.FLUSH_NOCASCADE_SYNC:
+                return "flush-sync";
+            case DataChannel.FLUSH_CASCADE_SYNC:
+                return "flush-cascade-sync";
+            case DataChannel.ROLLBACK_CASCADE_SYNC:
+                return "rollback-cascade-sync";
+            default:
+                return "unknown-sync";
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java
new file mode 100644
index 0000000..2357b2e
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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.remote.hessian;
+
+import com.caucho.hessian.io.AbstractSerializerFactory;
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+
+// This class is an ugly workaround for Hessian 4 bug with not loading custom deserializers.
+// TODO: once it is fixed in Hessian, remove this class
+class CayenneSerializerFactory extends com.caucho.hessian.io.SerializerFactory {
+    @Override
+    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+        for (int i = 0; _factories != null && i < _factories.size(); i++) {
+            AbstractSerializerFactory factory;
+            factory = (AbstractSerializerFactory) _factories.get(i);
+
+            Deserializer deserializer = factory.getDeserializer(cl);
+            if (deserializer != null) {
+                return deserializer;
+            }
+        }
+        
+        return super.getDeserializer(cl);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java
new file mode 100644
index 0000000..550ef09
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java
@@ -0,0 +1,114 @@
+/*****************************************************************
+ *   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.remote.hessian;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.util.Util;
+
+import com.caucho.hessian.io.AbstractSerializerFactory;
+import com.caucho.hessian.io.SerializerFactory;
+
+/**
+ * A utility class that configures Hessian serialization properties using reflection.
+ * 
+ * @since 1.2
+ */
+public class HessianConfig {
+
+    /**
+     * Creates a Hessian SerializerFactory configured with zero or more
+     * AbstractSerializerFactory extensions. Extensions are specified as class names. This
+     * method can inject EntityResolver if an extension factory class defines
+     * <em>setEntityResolver(EntityResolver)</em> method.
+     * 
+     * @param factoryNames an array of factory class names. Each class must be a concrete
+     *            subclass of <em>com.caucho.hessian.io.AbstractSerializerFactory</em>
+     *            and have a default constructor.
+     * @param resolver if not null, EntityResolver will be injected into all factories
+     *            that implement <em>setEntityResolver(EntityResolver)</em> method.
+     */
+    public static SerializerFactory createFactory(
+            String[] factoryNames,
+            EntityResolver resolver) {
+
+        SerializerFactory factory = new CayenneSerializerFactory();
+
+        if (factoryNames != null && factoryNames.length > 0) {
+
+            for (String factoryName : factoryNames) {
+
+                try {
+                    factory.addFactory(loadFactory(factoryName, resolver));
+                }
+                catch (Exception e) {
+                    throw new CayenneRuntimeException("Error configuring factory class "
+                            + factoryName, e);
+                }
+            }
+        }
+
+        return factory;
+    }
+
+    static AbstractSerializerFactory loadFactory(
+            String factoryName,
+            EntityResolver resolver) throws Exception {
+
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        Class factoryClass = Class.forName(factoryName, true, loader);
+
+        if (!AbstractSerializerFactory.class.isAssignableFrom(factoryClass)) {
+            throw new IllegalArgumentException(factoryClass
+                    + " is not a AbstractSerializerFactory");
+        }
+
+        Constructor c = factoryClass.getDeclaredConstructor();
+        if (!Util.isAccessible(c)) {
+            c.setAccessible(true);
+        }
+
+        AbstractSerializerFactory object = (AbstractSerializerFactory) c.newInstance();
+
+        if (resolver != null) {
+            try {
+
+                Method setter = factoryClass.getDeclaredMethod(
+                        "setEntityResolver",
+                        EntityResolver.class);
+
+                if (!Util.isAccessible(setter)) {
+                    setter.setAccessible(true);
+                }
+
+                setter.invoke(object, resolver);
+            }
+            catch (Exception e) {
+                // ignore injection exception
+            }
+        }
+
+        return object;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java
new file mode 100644
index 0000000..5409e43
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java
@@ -0,0 +1,64 @@
+/*****************************************************************
+ *   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.remote.hessian.service;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.ObjectContextFactory;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.remote.service.HttpRemoteService;
+
+import com.caucho.services.server.ServiceContext;
+
+/**
+ * An implementation of RemoteService for work within Caucho Hessian environment.
+ * 
+ * @since 3.1 the service API is reworked to initialize via Cayenne DI.
+ */
+public class HessianService extends HttpRemoteService {
+
+    public static final String[] SERVER_SERIALIZER_FACTORIES = new String[] {
+        ServerSerializerFactory.class.getName()
+    };
+
+    /**
+     * @since 3.1
+     */
+    public HessianService(@Inject ObjectContextFactory contextFactory,
+            @Inject(Constants.SERVER_ROP_EVENT_BRIDGE_PROPERTIES_MAP) Map<String, String> eventBridgeProperties) {
+        super(contextFactory, eventBridgeProperties);
+    }
+
+    @Override
+    protected HttpSession getSession(boolean create) {
+        HttpServletRequest request = (HttpServletRequest) ServiceContext
+                .getContextRequest();
+        if (request == null) {
+            throw new IllegalStateException(
+                    "Attempt to access HttpSession outside the request scope.");
+        }
+
+        return request.getSession(create);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java
new file mode 100644
index 0000000..bcd9f52
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   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.remote.hessian.service;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.cayenne.DataRow;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.AbstractSerializer;
+
+/**
+ * A server-side DataRow Hessian serializer.
+ */
+class ServerDataRowSerializer extends AbstractSerializer {
+
+    @Override
+    public void writeObject(Object object, AbstractHessianOutput out) throws IOException {
+        if (out.addRef(object)) {
+            return;
+        }
+
+        DataRow row = (DataRow) object;
+
+        out.writeMapBegin(DataRow.class.getName());
+        
+        out.writeInt(row.size());
+        out.writeLong(row.getVersion());
+        out.writeLong(row.getReplacesVersion());
+
+        for (final Map.Entry<String, Object> entry : row.entrySet()) {
+            out.writeObject(entry.getKey());
+            out.writeObject(entry.getValue());
+        }
+
+        out.writeMapEnd();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java
new file mode 100644
index 0000000..cf31ef0
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.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.remote.hessian.service;
+
+import java.io.IOException;
+
+import org.apache.cayenne.util.PersistentObjectList;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.JavaSerializer;
+
+/**
+ * Serializer for PersistentObjectLists.
+ * 
+ * @since 1.2
+ */
+class ServerPersistentObjectListSerializer extends JavaSerializer {
+
+    ServerPersistentObjectListSerializer() {
+        super(PersistentObjectList.class);
+    }
+
+    @Override
+    public void writeObject(Object object, AbstractHessianOutput out) throws IOException {
+        PersistentObjectList list = (PersistentObjectList) object;
+        if (list.isFault()) {
+            out.writeNull();
+        }
+        else {
+            super.writeObject(object, out);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java
new file mode 100644
index 0000000..3310cee
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   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.remote.hessian.service;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.util.PersistentObjectList;
+import org.apache.cayenne.util.PersistentObjectMap;
+
+import com.caucho.hessian.io.AbstractSerializerFactory;
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+import com.caucho.hessian.io.JavaSerializer;
+import com.caucho.hessian.io.Serializer;
+
+/**
+ * An object that manages all custom (de)serializers used on the server.
+ * 
+ * @since 1.2
+ */
+public class ServerSerializerFactory extends AbstractSerializerFactory {
+    private ServerPersistentObjectListSerializer persistentObjectListSerializer;
+    private ServerDataRowSerializer dataRowSerilaizer;
+    private Serializer javaSerializer;
+
+    ServerSerializerFactory() {
+        this.persistentObjectListSerializer = new ServerPersistentObjectListSerializer();
+        this.dataRowSerilaizer = new ServerDataRowSerializer();
+    }
+
+    @Override
+    public Serializer getSerializer(Class cl) throws HessianProtocolException {
+
+        if (PersistentObjectList.class.isAssignableFrom(cl)) {
+            return persistentObjectListSerializer;
+        }
+        else if (DataRow.class.isAssignableFrom(cl)) {
+            return dataRowSerilaizer;
+        }
+        //turns out Hessian uses its own (incorrect) serialization mechanism for maps
+        else if (PersistentObjectMap.class.isAssignableFrom(cl)) {
+            if (javaSerializer == null) {
+                javaSerializer = new JavaSerializer(cl);
+            }
+            return javaSerializer;
+        }
+
+        return null;
+    }
+
+    @Override
+    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java
new file mode 100644
index 0000000..68fd89d
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java
@@ -0,0 +1,199 @@
+/*****************************************************************
+ *   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.remote.service;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.access.ClientServerChannel;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.ObjectContextFactory;
+import org.apache.cayenne.remote.ClientMessage;
+import org.apache.cayenne.remote.RemoteService;
+import org.apache.cayenne.remote.RemoteSession;
+import org.apache.cayenne.util.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.rmi.RemoteException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A generic implementation of an RemoteService. Can be subclassed to work with
+ * different remoting mechanisms, such as Hessian or JAXRPC.
+ * 
+ * @since 1.2
+ */
+public abstract class BaseRemoteService implements RemoteService {
+
+	// keep logger non-static so that it could be garbage collected with this
+	// instance.
+	protected final Logger logger;
+
+	protected ObjectContextFactory contextFactory;
+	protected String eventBridgeFactoryName;
+	protected Map<String, String> eventBridgeParameters;
+
+	/**
+	 * @since 3.1
+	 */
+	public BaseRemoteService(ObjectContextFactory contextFactory, Map<String, String> eventBridgeProperties) {
+
+		logger = LoggerFactory.getLogger(getClass());
+
+		// start Cayenne service
+		logger.debug("ROP service is starting");
+
+		this.contextFactory = contextFactory;
+		initEventBridgeParameters(eventBridgeProperties);
+
+		logger.debug(getClass().getName() + " started");
+	}
+
+	public String getEventBridgeFactoryName() {
+		return eventBridgeFactoryName;
+	}
+
+	public Map<String, String> getEventBridgeParameters() {
+		return eventBridgeParameters != null ? Collections.unmodifiableMap(eventBridgeParameters)
+				: Collections.EMPTY_MAP;
+	}
+
+	/**
+	 * Creates a new ServerSession with a dedicated DataChannel.
+	 */
+	protected abstract ServerSession createServerSession();
+
+	/**
+	 * Creates a new ServerSession based on a shared DataChannel.
+	 * 
+	 * @param name
+	 *            shared session name used to lookup a shared DataChannel.
+	 */
+	protected abstract ServerSession createServerSession(String name);
+
+	/**
+	 * Returns a ServerSession object that represents Cayenne-related state
+	 * associated with the current session. If ServerSession hasn't been
+	 * previously saved, returns null.
+	 */
+	protected abstract ServerSession getServerSession();
+
+	@Override
+	public RemoteSession establishSession() {
+		logger.debug("Session requested by client");
+
+		RemoteSession session = createServerSession().getSession();
+
+		logger.debug("Established client session: " + session);
+		return session;
+	}
+
+	@Override
+	public RemoteSession establishSharedSession(String name) {
+		logger.debug("Shared session requested by client. Group name: " + name);
+
+		if (name == null) {
+			throw new CayenneRuntimeException("Invalid null shared session name");
+		}
+
+		return createServerSession(name).getSession();
+	}
+
+	@Override
+	public Object processMessage(ClientMessage message) throws Throwable {
+
+		if (message == null) {
+			throw new IllegalArgumentException("Null client message.");
+		}
+
+		ServerSession handler = getServerSession();
+
+		if (handler == null) {
+			throw new MissingSessionException("No session associated with request.");
+		}
+
+		logger.debug("processMessage, sessionId: " + handler.getSession().getSessionId());
+
+		// intercept and log exceptions
+		try {
+			return DispatchHelper.dispatch(handler.getChannel(), message);
+		} catch (Throwable th) {
+
+			StringBuilder wrapperMessage = new StringBuilder();
+			wrapperMessage.append("Exception processing message ").append(message.getClass().getName())
+					.append(" of type ").append(message);
+
+			String wrapperMessageString = wrapperMessage.toString();
+			logger.info(wrapperMessageString, th);
+
+			// This exception will probably be propagated to the client.
+			// Recast the exception to a serializable form.
+			Exception cause = new Exception(Util.unwindException(th).getLocalizedMessage());
+
+			throw new CayenneRuntimeException(wrapperMessageString, cause);
+		}
+	}
+
+	@Override
+	public void close() throws RemoteException {
+	}
+
+	protected RemoteSession createRemoteSession(String sessionId, String name, boolean enableEvents) {
+		RemoteSession session = (enableEvents) ? new RemoteSession(sessionId, eventBridgeFactoryName,
+				eventBridgeParameters) : new RemoteSession(sessionId);
+
+		session.setName(name);
+		return session;
+	}
+
+	/**
+	 * Creates a server-side channel that will handle all client requests. For
+	 * shared sessions the same channel instance is reused for the entire group
+	 * of clients. For dedicated sessions, one channel per client is created.
+	 * <p>
+	 * This implementation returns {@link ClientServerChannel} instance wrapping
+	 * a DataContext. Subclasses may override the method to customize channel
+	 * creation. For instance they may wrap channel in the custom interceptors
+	 * to handle transactions or security.
+	 */
+	protected DataChannel createChannel() {
+		return new ClientServerChannel((DataContext) contextFactory.createContext());
+	}
+
+	/**
+	 * Initializes EventBridge parameters for remote clients peer-to-peer
+	 * communications.
+	 */
+	protected void initEventBridgeParameters(Map<String, String> properties) {
+		String eventBridgeFactoryName = properties.get(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY);
+
+		if (eventBridgeFactoryName != null) {
+
+			Map<String, String> eventBridgeParameters = new HashMap<>(properties);
+			eventBridgeParameters.remove(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY);
+
+			this.eventBridgeFactoryName = eventBridgeFactoryName;
+			this.eventBridgeParameters = eventBridgeParameters;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java
new file mode 100644
index 0000000..83b23e2
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.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.remote.service;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.remote.BootstrapMessage;
+import org.apache.cayenne.remote.ClientMessage;
+import org.apache.cayenne.remote.QueryMessage;
+import org.apache.cayenne.remote.SyncMessage;
+
+/**
+ * A helper class to match message types with DataChannel methods.
+ * 
+ * @since 1.2
+ */
+class DispatchHelper {
+
+    static Object dispatch(DataChannel channel, ClientMessage message) {
+        // do most common messages first...
+        if (message instanceof QueryMessage) {
+            return channel.onQuery(null, ((QueryMessage) message).getQuery());
+        } else if (message instanceof SyncMessage) {
+            SyncMessage sync = (SyncMessage) message;
+            return channel.onSync(null, sync.getSenderChanges(), sync.getType());
+        } else if (message instanceof BootstrapMessage) {
+            return channel.getEntityResolver().getClientEntityResolver();
+        } else {
+            throw new CayenneRuntimeException("Message dispatch error. Unsupported message: %s", message);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java
new file mode 100644
index 0000000..80bd9bd
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java
@@ -0,0 +1,134 @@
+/*****************************************************************
+ *   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.remote.service;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.configuration.ObjectContextFactory;
+import org.apache.cayenne.remote.RemoteSession;
+
+/**
+ * A {@link org.apache.cayenne.remote.RemoteService} implementation that stores server
+ * context information in HTTP sessions.
+ * 
+ * @since 1.2
+ */
+public abstract class HttpRemoteService extends BaseRemoteService {
+
+    static final String SESSION_ATTRIBUTE = HttpRemoteService.class.getName()
+            + ".ServerSession";
+
+    private Map<String, WeakReference<DataChannel>> sharedChannels;
+
+    /**
+     * @since 3.1
+     */
+    public HttpRemoteService(ObjectContextFactory contextFactory,
+            Map<String, String> eventBridgeProperties) {
+        super(contextFactory, eventBridgeProperties);
+        this.sharedChannels = new HashMap<>();
+    }
+
+    /**
+     * Returns an HttpSession associated with the current request in progress.
+     */
+    protected abstract HttpSession getSession(boolean create);
+
+    /**
+     * Returns a ServerSession object that represents Cayenne-related state associated
+     * with the current session. If ServerSession hasn't been previously saved, returns
+     * null.
+     */
+    @Override
+    protected ServerSession getServerSession() {
+        HttpSession httpSession = getSession(true);
+        return (ServerSession) httpSession.getAttribute(SESSION_ATTRIBUTE);
+    }
+
+    /**
+     * Creates a new ServerSession with a dedicated DataChannel. Returned ServerSession is
+     * stored in HttpSession for future reuse.
+     */
+    @Override
+    protected ServerSession createServerSession() {
+
+        HttpSession httpSession = getSession(true);
+
+        DataChannel channel = createChannel();
+        RemoteSession remoteSession = createRemoteSession(
+                httpSession.getId(),
+                null,
+                false);
+        ServerSession serverSession = new ServerSession(remoteSession, channel);
+
+        httpSession.setAttribute(SESSION_ATTRIBUTE, serverSession);
+        return serverSession;
+    }
+
+    /**
+     * Creates a new ServerSession based on a shared DataChannel. Returned ServerSession
+     * is stored in HttpSession for future reuse.
+     * 
+     * @param name shared session name used to lookup a shared DataChannel.
+     */
+    @Override
+    protected ServerSession createServerSession(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Name is null for shared session.");
+        }
+
+        HttpSession httpSession = getSession(true);
+        DataChannel channel;
+
+        synchronized (sharedChannels) {
+            channel = getSharedChannel(name);
+            if (channel == null) {
+                channel = createChannel();
+                saveSharedChannel(name, channel);
+                logger.debug("Starting a new shared channel: " + name);
+            }
+            else {
+                logger.debug("Joining existing shared channel: " + name);
+            }
+        }
+
+        RemoteSession remoteSession = createRemoteSession(httpSession.getId(), name, true);
+
+        ServerSession serverSession = new ServerSession(remoteSession, channel);
+        httpSession.setAttribute(SESSION_ATTRIBUTE, serverSession);
+        return serverSession;
+    }
+
+    protected DataChannel getSharedChannel(String name) {
+        WeakReference<DataChannel> ref = sharedChannels.get(name);
+        return (ref != null) ? ref.get() : null;
+    }
+
+    protected void saveSharedChannel(String name, DataChannel channel) {
+        // wrap value in a WeakReference so that channels can be deallocated when all
+        // sessions that reference this channel time out...
+        sharedChannels.put(name, new WeakReference<DataChannel>(channel));
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java
new file mode 100644
index 0000000..e46e50b
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java
@@ -0,0 +1,38 @@
+/*****************************************************************
+ *   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.remote.service;
+
+import org.apache.cayenne.CayenneRuntimeException;
+
+/**
+ * An exception that are thrown by the ROP server if the client are missing a session.
+ * 
+ * @since 3.0
+ */
+public class MissingSessionException extends CayenneRuntimeException {
+
+    public MissingSessionException() {
+        super();
+    }
+
+    public MissingSessionException(String messageFormat, Object... messageArgs) {
+        super(messageFormat, messageArgs);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java
new file mode 100644
index 0000000..2ae8904
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.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.remote.service;
+
+import java.io.Serializable;
+
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.remote.RemoteSession;
+
+/**
+ * An object that stores server side objects for the client session.
+ * 
+ * @since 1.2
+ */
+public class ServerSession implements Serializable {
+
+    protected RemoteSession session;
+    protected DataChannel channel;
+
+    public ServerSession(RemoteSession session, DataChannel channel) {
+        this.session = session;
+        this.channel = channel;
+    }
+
+    public DataChannel getChannel() {
+        return channel;
+    }
+
+    public RemoteSession getSession() {
+        return session;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java
new file mode 100644
index 0000000..1adfb52
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java
@@ -0,0 +1,67 @@
+/*****************************************************************
+ *   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;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * @since 4.0
+ */
+public class CayenneContextGraphManagerTest {
+
+    private CayenneContextGraphManager graphManager;
+
+    @Before
+    public void before() {
+        CayenneContext mockContext = mock(CayenneContext.class);
+        this.graphManager = new CayenneContextGraphManager(mockContext, false, false);
+    }
+
+    @Test
+    public void testRegisterNode() {
+
+        ObjectId id = new ObjectId("E1", "ID", 500);
+        Persistent object = mock(Persistent.class);
+
+        graphManager.registerNode(id, object);
+        assertSame(object, graphManager.getNode(id));
+    }
+
+    @Test
+    public void testUnregisterNode() {
+
+        ObjectId id = new ObjectId("E1", "ID", 500);
+        Persistent object = mock(Persistent.class);
+
+        graphManager.registerNode(id, object);
+        Object unregistered = graphManager.unregisterNode(id);
+        assertSame(object, unregistered);
+
+        verify(object, times(0)).setObjectId(null);
+        verify(object).setObjectContext(null);
+        verify(object).setPersistenceState(PersistenceState.TRANSIENT);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java
new file mode 100644
index 0000000..f1ecab2
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ *   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.remote;
+
+import java.rmi.RemoteException;
+
+public class MockRemoteService implements RemoteService {
+
+    public RemoteSession establishSession() throws RemoteException {
+        return null;
+    }
+
+    public RemoteSession establishSharedSession(String name) throws RemoteException {
+        return null;
+    }
+
+    public Object processMessage(ClientMessage message) throws RemoteException, Throwable {
+        return null;
+    }
+
+    @Override
+    public void close() throws RemoteException {
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java
new file mode 100644
index 0000000..869e110
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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.remote;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class RemoteSessionTest {
+
+    @Test
+    public void testConstructor1() {
+        RemoteSession descriptor = new RemoteSession("abc");
+        assertEquals("abc", descriptor.getSessionId());
+        assertFalse(descriptor.isServerEventsEnabled());
+    }
+
+    @Test
+    public void testConstructor2() {
+        RemoteSession descriptor = new RemoteSession("abc", "factory", null);
+        assertEquals("abc", descriptor.getSessionId());
+        assertTrue(descriptor.isServerEventsEnabled());
+    }
+
+    @Test
+    public void testHashCode() {
+        RemoteSession d1 = new RemoteSession("1");
+        RemoteSession d2 = new RemoteSession("1");
+
+        assertEquals(d1.hashCode(), d1.hashCode());
+        assertEquals(d1.hashCode(), d2.hashCode());
+
+        d2.setName("some name");
+        assertEquals(d1.hashCode(), d2.hashCode());
+
+        RemoteSession d3 = new RemoteSession("2");
+        assertFalse(d1.hashCode() == d3.hashCode());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java
new file mode 100644
index 0000000..4cf0d0b
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java
@@ -0,0 +1,61 @@
+/*****************************************************************
+ *   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.remote.hessian;
+
+import com.caucho.hessian.io.AbstractSerializerFactory;
+import com.caucho.hessian.io.SerializerFactory;
+import org.apache.cayenne.map.EntityResolver;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class HessianConfigTest {
+
+    @Test
+    public void testLoadFactoryNoExtensions() {
+        SerializerFactory factory = HessianConfig.createFactory(null, null);
+        assertNotNull(factory);
+    }
+
+    @Test
+    public void testLoadFactoryNoInjection() throws Exception {
+        AbstractSerializerFactory factory = HessianConfig.loadFactory(
+                MockAbstractSerializerFactory.class.getName(),
+                null);
+
+        assertTrue(factory instanceof MockAbstractSerializerFactory);
+        assertNull(((MockAbstractSerializerFactory) factory).getEntityResolver());
+    }
+
+    @Test
+    public void testLoadFactoryInjection() throws Exception {
+        EntityResolver resolver = new EntityResolver();
+        AbstractSerializerFactory factory = HessianConfig.loadFactory(
+                MockAbstractSerializerFactory.class.getName(),
+                resolver);
+
+        assertTrue(factory instanceof MockAbstractSerializerFactory);
+        assertSame(resolver, ((MockAbstractSerializerFactory) factory)
+                .getEntityResolver());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java
new file mode 100644
index 0000000..8e861ab
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   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.remote.hessian;
+
+import org.apache.cayenne.map.EntityResolver;
+
+import com.caucho.hessian.io.AbstractSerializerFactory;
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+import com.caucho.hessian.io.Serializer;
+
+public class MockAbstractSerializerFactory extends AbstractSerializerFactory {
+
+    protected EntityResolver entityResolver;
+
+    @Override
+    public Serializer getSerializer(Class cl) throws HessianProtocolException {
+        return null;
+    }
+
+    @Override
+    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+        return null;
+    }
+
+    public EntityResolver getEntityResolver() {
+        return entityResolver;
+    }
+
+    public void setEntityResolver(EntityResolver entityResolver) {
+        this.entityResolver = entityResolver;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java
new file mode 100644
index 0000000..590651d
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java
@@ -0,0 +1,73 @@
+/*****************************************************************
+ *   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.remote.hessian.service;
+
+import com.caucho.services.server.ServiceContext;
+import com.mockrunner.mock.web.MockHttpServletRequest;
+import com.mockrunner.mock.web.MockHttpSession;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.ObjectContextFactory;
+import org.apache.cayenne.event.MockEventBridgeFactory;
+import org.junit.Test;
+
+import javax.servlet.http.HttpSession;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertSame;
+
+public class HessianServiceTest {
+
+	@Test
+	public void testGetSession() throws Exception {
+
+		Map<String, String> map = new HashMap<>();
+		map.put(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY, MockEventBridgeFactory.class.getName());
+
+		ObjectContextFactory factory = new ObjectContextFactory() {
+
+			public ObjectContext createContext(DataChannel parent) {
+				return null;
+			}
+
+			public ObjectContext createContext() {
+				return null;
+			}
+		};
+		HessianService service = new HessianService(factory, map);
+
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		HttpSession session = new MockHttpSession();
+		request.setSession(session);
+
+		// for some reason need to call this to get session activated in the
+		// mock request
+		request.getSession();
+
+		try {
+			ServiceContext.begin(request, null, null, null);
+			assertSame(session, service.getSession(false));
+		} finally {
+			ServiceContext.end();
+		}
+	}
+}


[2/5] cayenne git commit: CAY-2373 cayenne-rop-server module - move org.apache.cayenne.remote package to cayenne-rop server module - remove dependencies from cayenne-server pom.xml - update tutorial

Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java
deleted file mode 100644
index 7ed1004..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteIncrementalFaultList.java
+++ /dev/null
@@ -1,668 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.QueryResponse;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.util.IDUtil;
-import org.apache.cayenne.util.IncrementalListResponse;
-import org.apache.cayenne.util.Util;
-
-/**
- * A list that serves as a container of Persistent objects. It is usually returned by an
- * ObjectContext when a paginated query is performed. Initially only the first "page" of
- * objects is fully resolved. Pages following the first page are resolved on demand. When
- * a list element is accessed, the list would ensure that this element as well as all its
- * siblings on the same page are fully resolved.
- * <p>
- * The list can hold DataRows or Persistent objects. Attempts to add any other object
- * types will result in an exception.
- * </p>
- * <p>
- * Certain operations like <code>toArray</code> would trigger full list fetch.
- * </p>
- * <p>
- * Synchronization Note: this list is not synchronized. All access to it should follow
- * synchronization rules applicable for ArrayList.
- * </p>
- * 
- * @since 1.2
- */
-public class RemoteIncrementalFaultList implements List {
-
-    static final Object PLACEHOLDER = new Object();
-
-    protected List elements;
-
-    protected String cacheKey;
-    protected int pageSize;
-    protected int unfetchedObjects;
-    protected Query paginatedQuery;
-
-    protected transient ObjectContext context;
-
-    /**
-     * Stores a hint allowing to distinguish data rows from unfetched ids when the query
-     * fetches data rows.
-     */
-    protected int rowWidth;
-
-    private ListHelper helper;
-
-    public RemoteIncrementalFaultList(ObjectContext context, Query paginatedQuery) {
-
-        QueryMetadata metadata = paginatedQuery.getMetaData(context.getEntityResolver());
-
-        if (metadata.getPageSize() <= 0) {
-            throw new IllegalArgumentException("Page size must be positive: "
-                    + metadata.getPageSize());
-        }
-
-        this.pageSize = metadata.getPageSize();
-        this.helper = (metadata.isFetchingDataRows())
-                ? new DataRowListHelper()
-                : new PersistentListHelper();
-        this.context = context;
-
-        // use provided cache key if possible; this would allow clients to
-        // address the same server-side list from multiple queries.
-        this.cacheKey = metadata.getCacheKey();
-        if (cacheKey == null) {
-            cacheKey = generateCacheKey();
-        }
-
-        Query query = paginatedQuery;
-
-        // always wrap a query in a Incremental*Query, to ensure cache key is
-        // client-generated (e.g. see CAY-1003 - client and server can be in different
-        // timezones, so the key can be messed up)
-
-        // there are some serious pagination optimizations for SelectQuery on the
-        // server-side, so use a special wrapper that is itself a subclass of
-        // SelectQuery
-        if (query instanceof SelectQuery) {
-            query = new IncrementalSelectQuery<Object>((SelectQuery<Object>) paginatedQuery, cacheKey);
-        }
-        else {
-            query = new IncrementalQuery(paginatedQuery, cacheKey);
-        }
-
-        // ensure that originating query is wrapped to include the right cache key....
-        this.paginatedQuery = query;
-
-        // select directly from the channel, bypassing the context. Otherwise our query
-        // wrapper can be intercepted incorrectly
-        QueryResponse response = context.getChannel().onQuery(context, query);
-
-        List firstPage = response.firstList();
-
-        // sanity check
-        if (firstPage.size() > pageSize) {
-            throw new IllegalArgumentException("Returned page size ("
-                    + firstPage.size()
-                    + ") exceeds requested page size ("
-                    + pageSize
-                    + ")");
-        }
-        // result is smaller than a page
-        else if (firstPage.size() < pageSize) {
-            this.elements = new ArrayList(firstPage);
-            unfetchedObjects = 0;
-        }
-        else {
-
-            if (response instanceof IncrementalListResponse) {
-                int fullListSize = ((IncrementalListResponse) response).getFullSize();
-
-                this.unfetchedObjects = fullListSize - firstPage.size();
-                this.elements = new ArrayList(fullListSize);
-                elements.addAll(firstPage);
-
-                // fill the rest with placeholder...
-                for (int i = pageSize; i < fullListSize; i++) {
-                    elements.add(PLACEHOLDER);
-                }
-            }
-            // this happens when full size equals page size
-            else {
-                this.elements = new ArrayList(firstPage);
-                unfetchedObjects = 0;
-            }
-        }
-    }
-
-    private String generateCacheKey() {
-        byte[] bytes = IDUtil.pseudoUniqueByteSequence8();
-        StringBuffer buffer = new StringBuffer(17);
-        buffer.append("I");
-        for (byte aByte : bytes) {
-            IDUtil.appendFormattedByte(buffer, aByte);
-        }
-
-        return buffer.toString();
-    }
-
-    /**
-     * Will resolve all unread objects.
-     */
-    public void resolveAll() {
-        resolveInterval(0, size());
-    }
-
-    /**
-     * @param object
-     * @return <code>true</code> if the object corresponds to an unresolved state and
-     *         does require a fetch before being returned to the user.
-     */
-    private boolean isUnresolved(Object object) {
-        return object == PLACEHOLDER;
-    }
-
-    /**
-     * Resolves a sublist of objects starting at <code>fromIndex</code> up to but not
-     * including <code>toIndex</code>. Internally performs bound checking and trims
-     * indexes accordingly.
-     */
-    protected void resolveInterval(int fromIndex, int toIndex) {
-        if (fromIndex >= toIndex || elements.isEmpty()) {
-            return;
-        }
-
-        if (context == null) {
-            throw new CayenneRuntimeException("No ObjectContext set, can't resolve objects.");
-        }
-
-        // bounds checking
-
-        if (fromIndex < 0) {
-            fromIndex = 0;
-        }
-
-        if (toIndex > elements.size()) {
-            toIndex = elements.size();
-        }
-
-        // find disjoint ranges and resolve them individually...
-
-        int fromPage = pageIndex(fromIndex);
-        int toPage = pageIndex(toIndex - 1);
-
-        int rangeStartIndex = -1;
-        for (int i = fromPage; i <= toPage; i++) {
-
-            int pageStartIndex = i * pageSize;
-            Object firstPageObject = elements.get(pageStartIndex);
-            if (isUnresolved(firstPageObject)) {
-
-                // start range
-                if (rangeStartIndex < 0) {
-                    rangeStartIndex = pageStartIndex;
-                }
-            }
-            else {
-
-                // finish range...
-                if (rangeStartIndex >= 0) {
-                    forceResolveInterval(rangeStartIndex, pageStartIndex);
-                    rangeStartIndex = -1;
-                }
-            }
-        }
-
-        // load last page
-        if (rangeStartIndex >= 0) {
-            forceResolveInterval(rangeStartIndex, toIndex);
-        }
-    }
-
-    void forceResolveInterval(int fromIndex, int toIndex) {
-
-        int pastEnd = toIndex - size();
-        if (pastEnd > 0) {
-            toIndex = size();
-        }
-
-        int fetchLimit = toIndex - fromIndex;
-
-        RangeQuery query = new RangeQuery(cacheKey, fromIndex, fetchLimit, paginatedQuery);
-
-        List sublist = context.performQuery(query);
-
-        // sanity check
-        if (sublist.size() != fetchLimit) {
-            throw new CayenneRuntimeException("Resolved range size %d is not the same as expected: %d"
-                    , sublist.size(), fetchLimit);
-        }
-
-        for (int i = 0; i < fetchLimit; i++) {
-            elements.set(fromIndex + i, sublist.get(i));
-        }
-
-        unfetchedObjects -= sublist.size();
-    }
-
-    /**
-     * Returns zero-based index of the virtual "page" for a given array element index.
-     */
-    int pageIndex(int elementIndex) {
-        if (elementIndex < 0 || elementIndex > size()) {
-            throw new IndexOutOfBoundsException("Index: " + elementIndex);
-        }
-
-        if (pageSize <= 0 || elementIndex < 0) {
-            return -1;
-        }
-
-        return elementIndex / pageSize;
-    }
-
-    /**
-     * Returns ObjectContext associated with this list.
-     */
-    public ObjectContext getContext() {
-        return context;
-    }
-
-    /**
-     * Returns the pageSize.
-     * 
-     * @return int
-     */
-    public int getPageSize() {
-        return pageSize;
-    }
-
-    /**
-     * Returns a list iterator for this list. DataObjects are resolved a page (according
-     * to getPageSize()) at a time as necessary - when retrieved with next() or
-     * previous().
-     */
-    public ListIterator listIterator() {
-        return new ListIteratorHelper(0);
-    }
-
-    /**
-     * Returns a list iterator of the elements in this list (in proper sequence), starting
-     * at the specified position in this list. The specified index indicates the first
-     * element that would be returned by an initial call to the next method. An initial
-     * call to the previous method would return the element with the specified index minus
-     * one. DataObjects are resolved a page at a time (according to getPageSize()) as
-     * necessary - when retrieved with next() or previous().
-     */
-    public ListIterator listIterator(int index) {
-        if (index < 0 || index > size()) {
-            throw new IndexOutOfBoundsException("Index: " + index);
-        }
-
-        return new ListIteratorHelper(index);
-    }
-
-    /**
-     * Return an iterator for this list. DataObjects are resolved a page (according to
-     * getPageSize()) at a time as necessary - when retrieved with next().
-     */
-    public Iterator iterator() {
-        // by virtue of get(index)'s implementation, resolution of ids into
-        // objects will occur on pageSize boundaries as necessary.
-        return new Iterator() {
-
-            int listIndex = 0;
-
-            public boolean hasNext() {
-                return (listIndex < elements.size());
-            }
-
-            public Object next() {
-                if (listIndex >= elements.size())
-                    throw new NoSuchElementException("no more elements");
-
-                return get(listIndex++);
-            }
-
-            public void remove() {
-                throw new UnsupportedOperationException("remove not supported.");
-            }
-        };
-    }
-
-    /**
-     * @see java.util.List#add(int, Object)
-     */
-    public void add(int index, Object element) {
-        helper.validateListObject(element);
-        elements.add(index, element);
-
-    }
-
-    /**
-     * @see java.util.Collection#add(Object)
-     */
-    public boolean add(Object o) {
-        helper.validateListObject(o);
-        return elements.add(o);
-    }
-
-    /**
-     * @see java.util.Collection#addAll(Collection)
-     */
-    public boolean addAll(Collection c) {
-
-        return elements.addAll(c);
-
-    }
-
-    /**
-     * @see java.util.List#addAll(int, Collection)
-     */
-    public boolean addAll(int index, Collection c) {
-
-        return elements.addAll(index, c);
-
-    }
-
-    /**
-     * @see java.util.Collection#clear()
-     */
-    public void clear() {
-        elements.clear();
-    }
-
-    /**
-     * @see java.util.Collection#contains(Object)
-     */
-    public boolean contains(Object o) {
-        return indexOf(o) >= 0;
-    }
-
-    /**
-     * @see java.util.Collection#containsAll(Collection)
-     */
-    public boolean containsAll(Collection c) {
-        Iterator it = c.iterator();
-        while (it.hasNext()) {
-            if (!contains(it.next())) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * @see java.util.List#get(int)
-     */
-    public Object get(int index) {
-
-        Object o = elements.get(index);
-
-        if (isUnresolved(o)) {
-            // read this page
-            int pageStart = pageIndex(index) * pageSize;
-            resolveInterval(pageStart, pageStart + pageSize);
-
-            return elements.get(index);
-        }
-        else {
-            return o;
-        }
-
-    }
-
-    /**
-     * @see java.util.List#indexOf(Object)
-     */
-    public int indexOf(Object o) {
-        return helper.indexOfObject(o);
-    }
-
-    /**
-     * @see java.util.Collection#isEmpty()
-     */
-    public boolean isEmpty() {
-
-        return elements.isEmpty();
-
-    }
-
-    /**
-     * @see java.util.List#lastIndexOf(Object)
-     */
-    public int lastIndexOf(Object o) {
-        return helper.lastIndexOfObject(o);
-    }
-
-    /**
-     * @see java.util.List#remove(int)
-     */
-    public Object remove(int index) {
-
-        return elements.remove(index);
-
-    }
-
-    /**
-     * @see java.util.Collection#remove(Object)
-     */
-    public boolean remove(Object o) {
-
-        return elements.remove(o);
-
-    }
-
-    /**
-     * @see java.util.Collection#removeAll(Collection)
-     */
-    public boolean removeAll(Collection c) {
-
-        return elements.removeAll(c);
-
-    }
-
-    /**
-     * @see java.util.Collection#retainAll(Collection)
-     */
-    public boolean retainAll(Collection c) {
-
-        return elements.retainAll(c);
-
-    }
-
-    /**
-     * @see java.util.List#set(int, Object)
-     */
-    public Object set(int index, Object element) {
-        helper.validateListObject(element);
-
-        return elements.set(index, element);
-
-    }
-
-    /**
-     * @see java.util.Collection#size()
-     */
-    public int size() {
-        return elements.size();
-    }
-
-    public List subList(int fromIndex, int toIndex) {
-        resolveInterval(fromIndex, toIndex);
-        return elements.subList(fromIndex, toIndex);
-    }
-
-    public Object[] toArray() {
-        resolveAll();
-
-        return elements.toArray();
-    }
-
-    /**
-     * @see java.util.Collection#toArray(Object[])
-     */
-    public Object[] toArray(Object[] a) {
-        resolveAll();
-
-        return elements.toArray(a);
-    }
-
-    /**
-     * Returns a total number of objects that are not resolved yet.
-     */
-    public int getUnfetchedObjects() {
-        return unfetchedObjects;
-    }
-
-    abstract class ListHelper {
-
-        int indexOfObject(Object object) {
-            if (incorrectObjectType(object)) {
-                return -1;
-            }
-
-            for (int i = 0; i < elements.size(); i++) {
-
-                if (Util.nullSafeEquals(object, get(i))) {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
-
-        int lastIndexOfObject(Object object) {
-            if (incorrectObjectType(object)) {
-                return -1;
-            }
-
-            for (int i = elements.size() - 1; i >= 0; i--) {
-                if (Util.nullSafeEquals(object, get(i))) {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
-
-        abstract boolean incorrectObjectType(Object object);
-
-        void validateListObject(Object object) throws IllegalArgumentException {
-            if (incorrectObjectType(object)) {
-                throw new IllegalArgumentException("Can't store this object: " + object);
-            }
-        }
-    }
-
-    class PersistentListHelper extends ListHelper {
-
-        @Override
-        boolean incorrectObjectType(Object object) {
-            if (!(object instanceof Persistent)) {
-                return true;
-            }
-
-            Persistent persistent = (Persistent) object;
-            if (persistent.getObjectContext() != context) {
-                return true;
-            }
-
-            return false;
-        }
-
-    }
-
-    class DataRowListHelper extends ListHelper {
-
-        @Override
-        boolean incorrectObjectType(Object object) {
-            if (!(object instanceof Map)) {
-                return true;
-            }
-
-            Map map = (Map) object;
-            return map.size() != rowWidth;
-        }
-    }
-
-    class ListIteratorHelper implements ListIterator {
-
-        // by virtue of get(index)'s implementation, resolution of ids into
-        // objects will occur on pageSize boundaries as necessary.
-
-        int listIndex;
-
-        public ListIteratorHelper(int startIndex) {
-            this.listIndex = startIndex;
-        }
-
-        public void add(Object o) {
-            throw new UnsupportedOperationException("add operation not supported");
-        }
-
-        public boolean hasNext() {
-            return (listIndex < elements.size());
-        }
-
-        public boolean hasPrevious() {
-            return (listIndex > 0);
-        }
-
-        public Object next() {
-            if (listIndex >= elements.size())
-                throw new NoSuchElementException("at the end of the list");
-
-            return get(listIndex++);
-        }
-
-        public int nextIndex() {
-            return listIndex;
-        }
-
-        public Object previous() {
-            if (listIndex < 1)
-                throw new NoSuchElementException("at the beginning of the list");
-
-            return get(--listIndex);
-        }
-
-        public int previousIndex() {
-            return (listIndex - 1);
-        }
-
-        public void remove() {
-            throw new UnsupportedOperationException("remove operation not supported");
-        }
-
-        public void set(Object o) {
-            throw new UnsupportedOperationException("set operation not supported");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteService.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteService.java
deleted file mode 100644
index f357846..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteService.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************
- * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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.remote;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * Interface of a Cayenne remote service.
- *
- * @since 1.2
- * @see org.apache.cayenne.rop.ROPServlet
- */
-public interface RemoteService extends Remote {
-
-    /**
-     * Establishes a dedicated session with Cayenne DataChannel, returning session id.
-     */
-    RemoteSession establishSession() throws RemoteException;
-
-    /**
-     * Creates a new session with the specified or joins an existing one. This method is
-     * used to bootstrap collaborating clients of a single "group chat".
-     */
-    RemoteSession establishSharedSession(String name) throws RemoteException;
-
-    /**
-     * Processes message on a remote server, returning the result of such processing.
-     */
-    Object processMessage(ClientMessage message) throws RemoteException, Throwable;
-
-    /**
-     * Close remote service resources.
-     * @sine 4.0
-     */
-    void close() throws RemoteException;
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java
deleted file mode 100644
index 461e79d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/RemoteSession.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.event.EventBridge;
-import org.apache.cayenne.event.EventBridgeFactory;
-import org.apache.cayenne.event.EventSubject;
-import org.apache.cayenne.util.HashCodeBuilder;
-import org.apache.cayenne.util.ToStringBuilder;
-
-/**
- * A descriptor used by default service implementation to pass session parameters to the
- * client. It provides the client with details on how to invoke the service and how to
- * listen for the server events.
- * 
- * @since 1.2
- */
-public class RemoteSession implements Serializable {
-
-    static final Collection<EventSubject> SUBJECTS = Arrays.asList(
-            DataChannel.GRAPH_CHANGED_SUBJECT,
-            DataChannel.GRAPH_FLUSHED_SUBJECT,
-            DataChannel.GRAPH_ROLLEDBACK_SUBJECT);
-
-    protected String name;
-    protected String sessionId;
-
-    protected String eventBridgeFactory;
-    protected Map<String, String> eventBridgeParameters;
-
-    // private constructor used by hessian deserialization mechanism
-    @SuppressWarnings("unused")
-    private RemoteSession() {
-
-    }
-
-    /**
-     * Creates a HessianServiceDescriptor without server events support.
-     */
-    public RemoteSession(String sessionId) {
-        this(sessionId, null, null);
-    }
-
-    /**
-     * Creates a HessianServiceDescriptor. If <code>eventBridgeFactory</code> argument
-     * is not null, session will support server events.
-     */
-    public RemoteSession(String sessionId, String eventBridgeFactory,
-            Map<String, String> eventBridgeParameters) {
-
-        if (sessionId == null) {
-            throw new IllegalArgumentException("Null sessionId");
-        }
-
-        this.sessionId = sessionId;
-        this.eventBridgeFactory = eventBridgeFactory;
-        this.eventBridgeParameters = eventBridgeParameters;
-    }
-
-    @Override
-    public int hashCode() {
-        return new HashCodeBuilder(71, 5).append(sessionId).toHashCode();
-    }
-
-    /**
-     * Returns server session id. This is often the same as HttpSession id.
-     */
-    public String getSessionId() {
-        return sessionId;
-    }
-
-    /**
-     * Returns session group name. Group name is used for shared sessions.
-     */
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public boolean isServerEventsEnabled() {
-        return eventBridgeFactory != null;
-    }
-
-    @Override
-    public String toString() {
-        ToStringBuilder builder = new ToStringBuilder(this)
-                .append("sessionId", sessionId);
-
-        if (eventBridgeFactory != null) {
-            builder.append("eventBridgeFactory", eventBridgeFactory);
-        }
-
-        if (name != null) {
-            builder.append("name", name);
-        }
-
-        return builder.toString();
-    }
-
-    public static Collection<EventSubject> getSubjects() {
-        return SUBJECTS;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public String getEventBridgeFactory() {
-        return eventBridgeFactory;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public Map<String, String> getEventBridgeParameters() {
-        return eventBridgeParameters != null ? eventBridgeParameters : Collections.<String, String> emptyMap();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java
deleted file mode 100644
index d8d5f37..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/SyncMessage.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.graph.GraphDiff;
-
-/**
- * A message used for synchronization of the child with parent. It defines a few types of
- * synchronization: "flush" (when the child sends its changes without a commit), "commit"
- * (cascading flush with ultimate commit to the database), and "rollback" - cascading
- * reverting of all uncommitted changes.
- * 
- * @since 1.2
- */
-public class SyncMessage implements ClientMessage {
-
-    protected transient ObjectContext source;
-    protected int type;
-    protected GraphDiff senderChanges;
-
-    // private constructor for Hessian deserialization
-    @SuppressWarnings("unused")
-    private SyncMessage() {
-
-    }
-
-    public SyncMessage(ObjectContext source, int syncType, GraphDiff senderChanges) {
-        // validate type
-        if (syncType != DataChannel.FLUSH_NOCASCADE_SYNC
-                && syncType != DataChannel.FLUSH_CASCADE_SYNC
-                && syncType != DataChannel.ROLLBACK_CASCADE_SYNC) {
-            throw new IllegalArgumentException("'type' is invalid: " + syncType);
-        }
-
-        this.source = source;
-        this.type = syncType;
-        this.senderChanges = senderChanges;
-    }
-
-    /**
-     * Returns a source of SyncMessage.
-     */
-    public ObjectContext getSource() {
-        return source;
-    }
-
-    public int getType() {
-        return type;
-    }
-
-    public GraphDiff getSenderChanges() {
-        return senderChanges;
-    }
-
-    /**
-     * Returns a description of the type of message.
-     * Possibilities are "flush-sync", "flush-cascade-sync", "rollback-cascade-sync" or "unknown-sync".
-     */
-    @Override
-    public String toString() {
-        switch (type) {
-            case DataChannel.FLUSH_NOCASCADE_SYNC:
-                return "flush-sync";
-            case DataChannel.FLUSH_CASCADE_SYNC:
-                return "flush-cascade-sync";
-            case DataChannel.ROLLBACK_CASCADE_SYNC:
-                return "rollback-cascade-sync";
-            default:
-                return "unknown-sync";
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java
deleted file mode 100644
index 98a794c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/CayenneSerializerFactory.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian;
-
-import com.caucho.hessian.io.AbstractSerializerFactory;
-import com.caucho.hessian.io.Deserializer;
-import com.caucho.hessian.io.HessianProtocolException;
-
-// This class is an ugly workaround for Hessian 4 bug with not loading custom deserializers.
-// TODO: once it is fixed in Hessian, remove this class
-class CayenneSerializerFactory extends com.caucho.hessian.io.SerializerFactory {
-    @Override
-    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
-        for (int i = 0; _factories != null && i < _factories.size(); i++) {
-            AbstractSerializerFactory factory;
-            factory = (AbstractSerializerFactory) _factories.get(i);
-
-            Deserializer deserializer = factory.getDeserializer(cl);
-            if (deserializer != null) {
-                return deserializer;
-            }
-        }
-        
-        return super.getDeserializer(cl);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java
deleted file mode 100644
index 550ef09..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/HessianConfig.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.util.Util;
-
-import com.caucho.hessian.io.AbstractSerializerFactory;
-import com.caucho.hessian.io.SerializerFactory;
-
-/**
- * A utility class that configures Hessian serialization properties using reflection.
- * 
- * @since 1.2
- */
-public class HessianConfig {
-
-    /**
-     * Creates a Hessian SerializerFactory configured with zero or more
-     * AbstractSerializerFactory extensions. Extensions are specified as class names. This
-     * method can inject EntityResolver if an extension factory class defines
-     * <em>setEntityResolver(EntityResolver)</em> method.
-     * 
-     * @param factoryNames an array of factory class names. Each class must be a concrete
-     *            subclass of <em>com.caucho.hessian.io.AbstractSerializerFactory</em>
-     *            and have a default constructor.
-     * @param resolver if not null, EntityResolver will be injected into all factories
-     *            that implement <em>setEntityResolver(EntityResolver)</em> method.
-     */
-    public static SerializerFactory createFactory(
-            String[] factoryNames,
-            EntityResolver resolver) {
-
-        SerializerFactory factory = new CayenneSerializerFactory();
-
-        if (factoryNames != null && factoryNames.length > 0) {
-
-            for (String factoryName : factoryNames) {
-
-                try {
-                    factory.addFactory(loadFactory(factoryName, resolver));
-                }
-                catch (Exception e) {
-                    throw new CayenneRuntimeException("Error configuring factory class "
-                            + factoryName, e);
-                }
-            }
-        }
-
-        return factory;
-    }
-
-    static AbstractSerializerFactory loadFactory(
-            String factoryName,
-            EntityResolver resolver) throws Exception {
-
-        ClassLoader loader = Thread.currentThread().getContextClassLoader();
-        Class factoryClass = Class.forName(factoryName, true, loader);
-
-        if (!AbstractSerializerFactory.class.isAssignableFrom(factoryClass)) {
-            throw new IllegalArgumentException(factoryClass
-                    + " is not a AbstractSerializerFactory");
-        }
-
-        Constructor c = factoryClass.getDeclaredConstructor();
-        if (!Util.isAccessible(c)) {
-            c.setAccessible(true);
-        }
-
-        AbstractSerializerFactory object = (AbstractSerializerFactory) c.newInstance();
-
-        if (resolver != null) {
-            try {
-
-                Method setter = factoryClass.getDeclaredMethod(
-                        "setEntityResolver",
-                        EntityResolver.class);
-
-                if (!Util.isAccessible(setter)) {
-                    setter.setAccessible(true);
-                }
-
-                setter.invoke(object, resolver);
-            }
-            catch (Exception e) {
-                // ignore injection exception
-            }
-        }
-
-        return object;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java
deleted file mode 100644
index 5409e43..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/HessianService.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian.service;
-
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ObjectContextFactory;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.remote.service.HttpRemoteService;
-
-import com.caucho.services.server.ServiceContext;
-
-/**
- * An implementation of RemoteService for work within Caucho Hessian environment.
- * 
- * @since 3.1 the service API is reworked to initialize via Cayenne DI.
- */
-public class HessianService extends HttpRemoteService {
-
-    public static final String[] SERVER_SERIALIZER_FACTORIES = new String[] {
-        ServerSerializerFactory.class.getName()
-    };
-
-    /**
-     * @since 3.1
-     */
-    public HessianService(@Inject ObjectContextFactory contextFactory,
-            @Inject(Constants.SERVER_ROP_EVENT_BRIDGE_PROPERTIES_MAP) Map<String, String> eventBridgeProperties) {
-        super(contextFactory, eventBridgeProperties);
-    }
-
-    @Override
-    protected HttpSession getSession(boolean create) {
-        HttpServletRequest request = (HttpServletRequest) ServiceContext
-                .getContextRequest();
-        if (request == null) {
-            throw new IllegalStateException(
-                    "Attempt to access HttpSession outside the request scope.");
-        }
-
-        return request.getSession(create);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java
deleted file mode 100644
index bcd9f52..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerDataRowSerializer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian.service;
-
-import java.io.IOException;
-import java.util.Map;
-
-import org.apache.cayenne.DataRow;
-
-import com.caucho.hessian.io.AbstractHessianOutput;
-import com.caucho.hessian.io.AbstractSerializer;
-
-/**
- * A server-side DataRow Hessian serializer.
- */
-class ServerDataRowSerializer extends AbstractSerializer {
-
-    @Override
-    public void writeObject(Object object, AbstractHessianOutput out) throws IOException {
-        if (out.addRef(object)) {
-            return;
-        }
-
-        DataRow row = (DataRow) object;
-
-        out.writeMapBegin(DataRow.class.getName());
-        
-        out.writeInt(row.size());
-        out.writeLong(row.getVersion());
-        out.writeLong(row.getReplacesVersion());
-
-        for (final Map.Entry<String, Object> entry : row.entrySet()) {
-            out.writeObject(entry.getKey());
-            out.writeObject(entry.getValue());
-        }
-
-        out.writeMapEnd();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java
deleted file mode 100644
index cf31ef0..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerPersistentObjectListSerializer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian.service;
-
-import java.io.IOException;
-
-import org.apache.cayenne.util.PersistentObjectList;
-
-import com.caucho.hessian.io.AbstractHessianOutput;
-import com.caucho.hessian.io.JavaSerializer;
-
-/**
- * Serializer for PersistentObjectLists.
- * 
- * @since 1.2
- */
-class ServerPersistentObjectListSerializer extends JavaSerializer {
-
-    ServerPersistentObjectListSerializer() {
-        super(PersistentObjectList.class);
-    }
-
-    @Override
-    public void writeObject(Object object, AbstractHessianOutput out) throws IOException {
-        PersistentObjectList list = (PersistentObjectList) object;
-        if (list.isFault()) {
-            out.writeNull();
-        }
-        else {
-            super.writeObject(object, out);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java
deleted file mode 100644
index 3310cee..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/hessian/service/ServerSerializerFactory.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian.service;
-
-import org.apache.cayenne.DataRow;
-import org.apache.cayenne.util.PersistentObjectList;
-import org.apache.cayenne.util.PersistentObjectMap;
-
-import com.caucho.hessian.io.AbstractSerializerFactory;
-import com.caucho.hessian.io.Deserializer;
-import com.caucho.hessian.io.HessianProtocolException;
-import com.caucho.hessian.io.JavaSerializer;
-import com.caucho.hessian.io.Serializer;
-
-/**
- * An object that manages all custom (de)serializers used on the server.
- * 
- * @since 1.2
- */
-public class ServerSerializerFactory extends AbstractSerializerFactory {
-    private ServerPersistentObjectListSerializer persistentObjectListSerializer;
-    private ServerDataRowSerializer dataRowSerilaizer;
-    private Serializer javaSerializer;
-
-    ServerSerializerFactory() {
-        this.persistentObjectListSerializer = new ServerPersistentObjectListSerializer();
-        this.dataRowSerilaizer = new ServerDataRowSerializer();
-    }
-
-    @Override
-    public Serializer getSerializer(Class cl) throws HessianProtocolException {
-
-        if (PersistentObjectList.class.isAssignableFrom(cl)) {
-            return persistentObjectListSerializer;
-        }
-        else if (DataRow.class.isAssignableFrom(cl)) {
-            return dataRowSerilaizer;
-        }
-        //turns out Hessian uses its own (incorrect) serialization mechanism for maps
-        else if (PersistentObjectMap.class.isAssignableFrom(cl)) {
-            if (javaSerializer == null) {
-                javaSerializer = new JavaSerializer(cl);
-            }
-            return javaSerializer;
-        }
-
-        return null;
-    }
-
-    @Override
-    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
-        return null;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java
deleted file mode 100644
index 68fd89d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/BaseRemoteService.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.access.ClientServerChannel;
-import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ObjectContextFactory;
-import org.apache.cayenne.remote.ClientMessage;
-import org.apache.cayenne.remote.RemoteService;
-import org.apache.cayenne.remote.RemoteSession;
-import org.apache.cayenne.util.Util;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.rmi.RemoteException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A generic implementation of an RemoteService. Can be subclassed to work with
- * different remoting mechanisms, such as Hessian or JAXRPC.
- * 
- * @since 1.2
- */
-public abstract class BaseRemoteService implements RemoteService {
-
-	// keep logger non-static so that it could be garbage collected with this
-	// instance.
-	protected final Logger logger;
-
-	protected ObjectContextFactory contextFactory;
-	protected String eventBridgeFactoryName;
-	protected Map<String, String> eventBridgeParameters;
-
-	/**
-	 * @since 3.1
-	 */
-	public BaseRemoteService(ObjectContextFactory contextFactory, Map<String, String> eventBridgeProperties) {
-
-		logger = LoggerFactory.getLogger(getClass());
-
-		// start Cayenne service
-		logger.debug("ROP service is starting");
-
-		this.contextFactory = contextFactory;
-		initEventBridgeParameters(eventBridgeProperties);
-
-		logger.debug(getClass().getName() + " started");
-	}
-
-	public String getEventBridgeFactoryName() {
-		return eventBridgeFactoryName;
-	}
-
-	public Map<String, String> getEventBridgeParameters() {
-		return eventBridgeParameters != null ? Collections.unmodifiableMap(eventBridgeParameters)
-				: Collections.EMPTY_MAP;
-	}
-
-	/**
-	 * Creates a new ServerSession with a dedicated DataChannel.
-	 */
-	protected abstract ServerSession createServerSession();
-
-	/**
-	 * Creates a new ServerSession based on a shared DataChannel.
-	 * 
-	 * @param name
-	 *            shared session name used to lookup a shared DataChannel.
-	 */
-	protected abstract ServerSession createServerSession(String name);
-
-	/**
-	 * Returns a ServerSession object that represents Cayenne-related state
-	 * associated with the current session. If ServerSession hasn't been
-	 * previously saved, returns null.
-	 */
-	protected abstract ServerSession getServerSession();
-
-	@Override
-	public RemoteSession establishSession() {
-		logger.debug("Session requested by client");
-
-		RemoteSession session = createServerSession().getSession();
-
-		logger.debug("Established client session: " + session);
-		return session;
-	}
-
-	@Override
-	public RemoteSession establishSharedSession(String name) {
-		logger.debug("Shared session requested by client. Group name: " + name);
-
-		if (name == null) {
-			throw new CayenneRuntimeException("Invalid null shared session name");
-		}
-
-		return createServerSession(name).getSession();
-	}
-
-	@Override
-	public Object processMessage(ClientMessage message) throws Throwable {
-
-		if (message == null) {
-			throw new IllegalArgumentException("Null client message.");
-		}
-
-		ServerSession handler = getServerSession();
-
-		if (handler == null) {
-			throw new MissingSessionException("No session associated with request.");
-		}
-
-		logger.debug("processMessage, sessionId: " + handler.getSession().getSessionId());
-
-		// intercept and log exceptions
-		try {
-			return DispatchHelper.dispatch(handler.getChannel(), message);
-		} catch (Throwable th) {
-
-			StringBuilder wrapperMessage = new StringBuilder();
-			wrapperMessage.append("Exception processing message ").append(message.getClass().getName())
-					.append(" of type ").append(message);
-
-			String wrapperMessageString = wrapperMessage.toString();
-			logger.info(wrapperMessageString, th);
-
-			// This exception will probably be propagated to the client.
-			// Recast the exception to a serializable form.
-			Exception cause = new Exception(Util.unwindException(th).getLocalizedMessage());
-
-			throw new CayenneRuntimeException(wrapperMessageString, cause);
-		}
-	}
-
-	@Override
-	public void close() throws RemoteException {
-	}
-
-	protected RemoteSession createRemoteSession(String sessionId, String name, boolean enableEvents) {
-		RemoteSession session = (enableEvents) ? new RemoteSession(sessionId, eventBridgeFactoryName,
-				eventBridgeParameters) : new RemoteSession(sessionId);
-
-		session.setName(name);
-		return session;
-	}
-
-	/**
-	 * Creates a server-side channel that will handle all client requests. For
-	 * shared sessions the same channel instance is reused for the entire group
-	 * of clients. For dedicated sessions, one channel per client is created.
-	 * <p>
-	 * This implementation returns {@link ClientServerChannel} instance wrapping
-	 * a DataContext. Subclasses may override the method to customize channel
-	 * creation. For instance they may wrap channel in the custom interceptors
-	 * to handle transactions or security.
-	 */
-	protected DataChannel createChannel() {
-		return new ClientServerChannel((DataContext) contextFactory.createContext());
-	}
-
-	/**
-	 * Initializes EventBridge parameters for remote clients peer-to-peer
-	 * communications.
-	 */
-	protected void initEventBridgeParameters(Map<String, String> properties) {
-		String eventBridgeFactoryName = properties.get(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY);
-
-		if (eventBridgeFactoryName != null) {
-
-			Map<String, String> eventBridgeParameters = new HashMap<>(properties);
-			eventBridgeParameters.remove(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY);
-
-			this.eventBridgeFactoryName = eventBridgeFactoryName;
-			this.eventBridgeParameters = eventBridgeParameters;
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java
deleted file mode 100644
index 83b23e2..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/DispatchHelper.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.remote.BootstrapMessage;
-import org.apache.cayenne.remote.ClientMessage;
-import org.apache.cayenne.remote.QueryMessage;
-import org.apache.cayenne.remote.SyncMessage;
-
-/**
- * A helper class to match message types with DataChannel methods.
- * 
- * @since 1.2
- */
-class DispatchHelper {
-
-    static Object dispatch(DataChannel channel, ClientMessage message) {
-        // do most common messages first...
-        if (message instanceof QueryMessage) {
-            return channel.onQuery(null, ((QueryMessage) message).getQuery());
-        } else if (message instanceof SyncMessage) {
-            SyncMessage sync = (SyncMessage) message;
-            return channel.onSync(null, sync.getSenderChanges(), sync.getType());
-        } else if (message instanceof BootstrapMessage) {
-            return channel.getEntityResolver().getClientEntityResolver();
-        } else {
-            throw new CayenneRuntimeException("Message dispatch error. Unsupported message: %s", message);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java
deleted file mode 100644
index 80bd9bd..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/HttpRemoteService.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.http.HttpSession;
-
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.configuration.ObjectContextFactory;
-import org.apache.cayenne.remote.RemoteSession;
-
-/**
- * A {@link org.apache.cayenne.remote.RemoteService} implementation that stores server
- * context information in HTTP sessions.
- * 
- * @since 1.2
- */
-public abstract class HttpRemoteService extends BaseRemoteService {
-
-    static final String SESSION_ATTRIBUTE = HttpRemoteService.class.getName()
-            + ".ServerSession";
-
-    private Map<String, WeakReference<DataChannel>> sharedChannels;
-
-    /**
-     * @since 3.1
-     */
-    public HttpRemoteService(ObjectContextFactory contextFactory,
-            Map<String, String> eventBridgeProperties) {
-        super(contextFactory, eventBridgeProperties);
-        this.sharedChannels = new HashMap<>();
-    }
-
-    /**
-     * Returns an HttpSession associated with the current request in progress.
-     */
-    protected abstract HttpSession getSession(boolean create);
-
-    /**
-     * Returns a ServerSession object that represents Cayenne-related state associated
-     * with the current session. If ServerSession hasn't been previously saved, returns
-     * null.
-     */
-    @Override
-    protected ServerSession getServerSession() {
-        HttpSession httpSession = getSession(true);
-        return (ServerSession) httpSession.getAttribute(SESSION_ATTRIBUTE);
-    }
-
-    /**
-     * Creates a new ServerSession with a dedicated DataChannel. Returned ServerSession is
-     * stored in HttpSession for future reuse.
-     */
-    @Override
-    protected ServerSession createServerSession() {
-
-        HttpSession httpSession = getSession(true);
-
-        DataChannel channel = createChannel();
-        RemoteSession remoteSession = createRemoteSession(
-                httpSession.getId(),
-                null,
-                false);
-        ServerSession serverSession = new ServerSession(remoteSession, channel);
-
-        httpSession.setAttribute(SESSION_ATTRIBUTE, serverSession);
-        return serverSession;
-    }
-
-    /**
-     * Creates a new ServerSession based on a shared DataChannel. Returned ServerSession
-     * is stored in HttpSession for future reuse.
-     * 
-     * @param name shared session name used to lookup a shared DataChannel.
-     */
-    @Override
-    protected ServerSession createServerSession(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("Name is null for shared session.");
-        }
-
-        HttpSession httpSession = getSession(true);
-        DataChannel channel;
-
-        synchronized (sharedChannels) {
-            channel = getSharedChannel(name);
-            if (channel == null) {
-                channel = createChannel();
-                saveSharedChannel(name, channel);
-                logger.debug("Starting a new shared channel: " + name);
-            }
-            else {
-                logger.debug("Joining existing shared channel: " + name);
-            }
-        }
-
-        RemoteSession remoteSession = createRemoteSession(httpSession.getId(), name, true);
-
-        ServerSession serverSession = new ServerSession(remoteSession, channel);
-        httpSession.setAttribute(SESSION_ATTRIBUTE, serverSession);
-        return serverSession;
-    }
-
-    protected DataChannel getSharedChannel(String name) {
-        WeakReference<DataChannel> ref = sharedChannels.get(name);
-        return (ref != null) ? ref.get() : null;
-    }
-
-    protected void saveSharedChannel(String name, DataChannel channel) {
-        // wrap value in a WeakReference so that channels can be deallocated when all
-        // sessions that reference this channel time out...
-        sharedChannels.put(name, new WeakReference<DataChannel>(channel));
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java
deleted file mode 100644
index e46e50b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/MissingSessionException.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import org.apache.cayenne.CayenneRuntimeException;
-
-/**
- * An exception that are thrown by the ROP server if the client are missing a session.
- * 
- * @since 3.0
- */
-public class MissingSessionException extends CayenneRuntimeException {
-
-    public MissingSessionException() {
-        super();
-    }
-
-    public MissingSessionException(String messageFormat, Object... messageArgs) {
-        super(messageFormat, messageArgs);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java
deleted file mode 100644
index 2ae8904..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/service/ServerSession.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- *   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.remote.service;
-
-import java.io.Serializable;
-
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.remote.RemoteSession;
-
-/**
- * An object that stores server side objects for the client session.
- * 
- * @since 1.2
- */
-public class ServerSession implements Serializable {
-
-    protected RemoteSession session;
-    protected DataChannel channel;
-
-    public ServerSession(RemoteSession session, DataChannel channel) {
-        this.session = session;
-        this.channel = channel;
-    }
-
-    public DataChannel getChannel() {
-        return channel;
-    }
-
-    public RemoteSession getSession() {
-        return session;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java b/cayenne-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java
deleted file mode 100644
index 1adfb52..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/CayenneContextGraphManagerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*****************************************************************
- *   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;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-/**
- * @since 4.0
- */
-public class CayenneContextGraphManagerTest {
-
-    private CayenneContextGraphManager graphManager;
-
-    @Before
-    public void before() {
-        CayenneContext mockContext = mock(CayenneContext.class);
-        this.graphManager = new CayenneContextGraphManager(mockContext, false, false);
-    }
-
-    @Test
-    public void testRegisterNode() {
-
-        ObjectId id = new ObjectId("E1", "ID", 500);
-        Persistent object = mock(Persistent.class);
-
-        graphManager.registerNode(id, object);
-        assertSame(object, graphManager.getNode(id));
-    }
-
-    @Test
-    public void testUnregisterNode() {
-
-        ObjectId id = new ObjectId("E1", "ID", 500);
-        Persistent object = mock(Persistent.class);
-
-        graphManager.registerNode(id, object);
-        Object unregistered = graphManager.unregisterNode(id);
-        assertSame(object, unregistered);
-
-        verify(object, times(0)).setObjectId(null);
-        verify(object).setObjectContext(null);
-        verify(object).setPersistenceState(PersistenceState.TRANSIENT);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java
deleted file mode 100644
index f1ecab2..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/MockRemoteService.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import java.rmi.RemoteException;
-
-public class MockRemoteService implements RemoteService {
-
-    public RemoteSession establishSession() throws RemoteException {
-        return null;
-    }
-
-    public RemoteSession establishSharedSession(String name) throws RemoteException {
-        return null;
-    }
-
-    public Object processMessage(ClientMessage message) throws RemoteException, Throwable {
-        return null;
-    }
-
-    @Override
-    public void close() throws RemoteException {
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java
deleted file mode 100644
index 869e110..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/RemoteSessionTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class RemoteSessionTest {
-
-    @Test
-    public void testConstructor1() {
-        RemoteSession descriptor = new RemoteSession("abc");
-        assertEquals("abc", descriptor.getSessionId());
-        assertFalse(descriptor.isServerEventsEnabled());
-    }
-
-    @Test
-    public void testConstructor2() {
-        RemoteSession descriptor = new RemoteSession("abc", "factory", null);
-        assertEquals("abc", descriptor.getSessionId());
-        assertTrue(descriptor.isServerEventsEnabled());
-    }
-
-    @Test
-    public void testHashCode() {
-        RemoteSession d1 = new RemoteSession("1");
-        RemoteSession d2 = new RemoteSession("1");
-
-        assertEquals(d1.hashCode(), d1.hashCode());
-        assertEquals(d1.hashCode(), d2.hashCode());
-
-        d2.setName("some name");
-        assertEquals(d1.hashCode(), d2.hashCode());
-
-        RemoteSession d3 = new RemoteSession("2");
-        assertFalse(d1.hashCode() == d3.hashCode());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java
deleted file mode 100644
index 4cf0d0b..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/HessianConfigTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian;
-
-import com.caucho.hessian.io.AbstractSerializerFactory;
-import com.caucho.hessian.io.SerializerFactory;
-import org.apache.cayenne.map.EntityResolver;
-import org.junit.Test;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-public class HessianConfigTest {
-
-    @Test
-    public void testLoadFactoryNoExtensions() {
-        SerializerFactory factory = HessianConfig.createFactory(null, null);
-        assertNotNull(factory);
-    }
-
-    @Test
-    public void testLoadFactoryNoInjection() throws Exception {
-        AbstractSerializerFactory factory = HessianConfig.loadFactory(
-                MockAbstractSerializerFactory.class.getName(),
-                null);
-
-        assertTrue(factory instanceof MockAbstractSerializerFactory);
-        assertNull(((MockAbstractSerializerFactory) factory).getEntityResolver());
-    }
-
-    @Test
-    public void testLoadFactoryInjection() throws Exception {
-        EntityResolver resolver = new EntityResolver();
-        AbstractSerializerFactory factory = HessianConfig.loadFactory(
-                MockAbstractSerializerFactory.class.getName(),
-                resolver);
-
-        assertTrue(factory instanceof MockAbstractSerializerFactory);
-        assertSame(resolver, ((MockAbstractSerializerFactory) factory)
-                .getEntityResolver());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java
deleted file mode 100644
index 8e861ab..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/MockAbstractSerializerFactory.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian;
-
-import org.apache.cayenne.map.EntityResolver;
-
-import com.caucho.hessian.io.AbstractSerializerFactory;
-import com.caucho.hessian.io.Deserializer;
-import com.caucho.hessian.io.HessianProtocolException;
-import com.caucho.hessian.io.Serializer;
-
-public class MockAbstractSerializerFactory extends AbstractSerializerFactory {
-
-    protected EntityResolver entityResolver;
-
-    @Override
-    public Serializer getSerializer(Class cl) throws HessianProtocolException {
-        return null;
-    }
-
-    @Override
-    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
-        return null;
-    }
-
-    public EntityResolver getEntityResolver() {
-        return entityResolver;
-    }
-
-    public void setEntityResolver(EntityResolver entityResolver) {
-        this.entityResolver = entityResolver;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java b/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java
deleted file mode 100644
index 590651d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/remote/hessian/service/HessianServiceTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*****************************************************************
- *   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.remote.hessian.service;
-
-import com.caucho.services.server.ServiceContext;
-import com.mockrunner.mock.web.MockHttpServletRequest;
-import com.mockrunner.mock.web.MockHttpSession;
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ObjectContextFactory;
-import org.apache.cayenne.event.MockEventBridgeFactory;
-import org.junit.Test;
-
-import javax.servlet.http.HttpSession;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertSame;
-
-public class HessianServiceTest {
-
-	@Test
-	public void testGetSession() throws Exception {
-
-		Map<String, String> map = new HashMap<>();
-		map.put(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY, MockEventBridgeFactory.class.getName());
-
-		ObjectContextFactory factory = new ObjectContextFactory() {
-
-			public ObjectContext createContext(DataChannel parent) {
-				return null;
-			}
-
-			public ObjectContext createContext() {
-				return null;
-			}
-		};
-		HessianService service = new HessianService(factory, map);
-
-		MockHttpServletRequest request = new MockHttpServletRequest();
-		HttpSession session = new MockHttpSession();
-		request.setSession(session);
-
-		// for some reason need to call this to get session activated in the
-		// mock request
-		request.getSession();
-
-		try {
-			ServiceContext.begin(request, null, null, null);
-			assertSame(session, service.getSession(false));
-		} finally {
-			ServiceContext.end();
-		}
-	}
-}


[3/5] cayenne git commit: CAY-2373 cayenne-rop-server module - move org.apache.cayenne.remote package to cayenne-rop server module - remove dependencies from cayenne-server pom.xml - update tutorial

Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java
new file mode 100644
index 0000000..00feca6
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/BaseRemoteServiceTest.java
@@ -0,0 +1,142 @@
+/*****************************************************************
+ *   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.remote.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.configuration.ObjectContextFactory;
+import org.apache.cayenne.event.MockEventBridgeFactory;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.remote.QueryMessage;
+import org.apache.cayenne.remote.RemoteSession;
+import org.apache.cayenne.util.Util;
+import org.junit.Test;
+
+public class BaseRemoteServiceTest {
+
+	@Test
+	public void testConstructor() throws Exception {
+
+		Map<String, String> map = new HashMap<>();
+		map.put(Constants.SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY, MockEventBridgeFactory.class.getName());
+
+		ObjectContextFactory factory = new ObjectContextFactory() {
+
+			public ObjectContext createContext(DataChannel parent) {
+				return null;
+			}
+
+			public ObjectContext createContext() {
+				return null;
+			}
+		};
+		BaseRemoteService service = new BaseRemoteService(factory, map) {
+
+			@Override
+			protected ServerSession createServerSession() {
+				return null;
+			}
+
+			@Override
+			protected ServerSession createServerSession(String name) {
+				return null;
+			}
+
+			@Override
+			protected ServerSession getServerSession() {
+				return null;
+			}
+		};
+		assertEquals(MockEventBridgeFactory.class.getName(), service.getEventBridgeFactoryName());
+		assertSame(factory, service.contextFactory);
+
+	}
+
+	@Test
+	public void testProcessMessageExceptionSerializability() throws Throwable {
+
+		Map<String, String> map = new HashMap<>();
+		ObjectContextFactory factory = new ObjectContextFactory() {
+
+			public ObjectContext createContext(DataChannel parent) {
+				return null;
+			}
+
+			public ObjectContext createContext() {
+				return null;
+			}
+		};
+		BaseRemoteService service = new BaseRemoteService(factory, map) {
+
+			@Override
+			protected ServerSession createServerSession() {
+				return new ServerSession(new RemoteSession("a"), null);
+			}
+
+			@Override
+			protected ServerSession createServerSession(String name) {
+				return createServerSession();
+			}
+
+			@Override
+			protected ServerSession getServerSession() {
+				return createServerSession();
+			}
+		};
+
+		try {
+			service.processMessage(new QueryMessage(null) {
+
+				@Override
+				public Query getQuery() {
+					// serializable exception thrown
+					throw new CayenneRuntimeException();
+				}
+			});
+
+			fail("Expected to throw");
+		} catch (Exception ex) {
+			Util.cloneViaSerialization(ex);
+		}
+
+		try {
+			service.processMessage(new QueryMessage(null) {
+
+				@Override
+				public Query getQuery() {
+					// non-serializable exception thrown
+					throw new MockUnserializableException();
+				}
+			});
+
+			fail("Expected to throw");
+		} catch (Exception ex) {
+			Util.cloneViaSerialization(ex);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.java
new file mode 100644
index 0000000..e4b01fe
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/DispatchHelperTest.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.remote.service;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.MockDataChannel;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.remote.BootstrapMessage;
+import org.apache.cayenne.remote.ClientMessage;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+public class DispatchHelperTest {
+
+    @Test
+    public void testBootstrapMessage() {
+        EntityResolver resolver = new EntityResolver();
+        MockDataChannel channel = new MockDataChannel(resolver);
+        assertSame(resolver.getClientEntityResolver(), DispatchHelper.dispatch(
+                channel,
+                new BootstrapMessage()));
+    }
+
+    @Test
+    public void testUnknownMessage() {
+        try {
+            DispatchHelper.dispatch(new MockDataChannel(), mock(ClientMessage.class));
+            fail("Unknown message must have failed");
+        }
+        catch (CayenneRuntimeException e) {
+            // expected
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java
new file mode 100644
index 0000000..e30435b
--- /dev/null
+++ b/cayenne-rop-server/src/test/java/org/apache/cayenne/remote/service/MockUnserializableException.java
@@ -0,0 +1,24 @@
+/*****************************************************************
+ *   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.remote.service;
+
+class MockUnserializableException extends RuntimeException {
+
+    protected Object notSerializableField = new Object();
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/pom.xml b/cayenne-server/pom.xml
index 4b7fb6b..a310a37 100644
--- a/cayenne-server/pom.xml
+++ b/cayenne-server/pom.xml
@@ -32,7 +32,6 @@
 	<dependencies>
 
 		<!-- Compile dependencies -->
-
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
@@ -44,31 +43,6 @@
 			<version>${project.version}</version>
 			<scope>compile</scope>
 		</dependency>
-		<!-- Optional dependencies... things that might have been placed in submodules... -->
-		<dependency>
-			<groupId>com.caucho</groupId>
-            <artifactId>hessian</artifactId>
-			<scope>provided</scope>
-			<optional>true</optional>
-		</dependency>
-		<dependency>
-			<groupId>opensymphony</groupId>
-			<artifactId>oscache</artifactId>
-			<scope>provided</scope>
-			<optional>true</optional>
-		</dependency>
-		<dependency>
-			<groupId>net.sf.ehcache</groupId>
-			<artifactId>ehcache-core</artifactId>
-			<scope>provided</scope>
-			<optional>true</optional>
-		</dependency>
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>servlet-api</artifactId>
-			<scope>provided</scope>
-			<optional>true</optional>
-		</dependency>
 
 		<!-- Test dependencies -->
 		<dependency>
@@ -110,11 +84,6 @@
 			</exclusions>
 		</dependency>
 		<dependency>
-			<groupId>com.mockrunner</groupId>
-			<artifactId>mockrunner-servlet</artifactId>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-simple</artifactId>
 			<scope>test</scope>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/CayenneContext.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContext.java b/cayenne-server/src/main/java/org/apache/cayenne/CayenneContext.java
deleted file mode 100644
index c352ae0..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContext.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*****************************************************************
- *   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;
-
-import org.apache.cayenne.event.EventManager;
-import org.apache.cayenne.graph.CompoundDiff;
-import org.apache.cayenne.graph.GraphDiff;
-import org.apache.cayenne.graph.GraphManager;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.util.EventUtil;
-import org.apache.cayenne.validation.ValidationException;
-import org.apache.cayenne.validation.ValidationResult;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * A default generic implementation of ObjectContext suitable for accessing
- * Cayenne from either an ORM or a client tiers. Communicates with Cayenne via a
- * {@link org.apache.cayenne.DataChannel}.
- * 
- * @since 1.2
- */
-public class CayenneContext extends BaseContext {
-
-    CayenneContextGraphManager graphManager;
-
-    // object that merges "backdoor" changes that come from the channel.
-    CayenneContextMergeHandler mergeHandler;
-
-    /**
-     * Creates a new CayenneContext with no channel and disabled graph events.
-     */
-    public CayenneContext() {
-        this(null);
-    }
-
-    /**
-     * Creates a new CayenneContext, initializing it with a channel instance.
-     * CayenneContext created using this constructor WILL NOT broadcast graph
-     * change events.
-     */
-    public CayenneContext(DataChannel channel) {
-        this(channel, false, false);
-    }
-
-    /**
-     * Creates a new CayenneContext, initializing it with a channel.
-     */
-    public CayenneContext(DataChannel channel, boolean changeEventsEnabled, boolean lifecyleEventsEnabled) {
-
-        graphManager = new CayenneContextGraphManager(this, changeEventsEnabled, lifecyleEventsEnabled);
-
-        if (channel != null) {
-            attachToChannel(channel);
-        }
-    }
-
-    /**
-     * @since 3.1
-     */
-    @Override
-    protected void attachToChannel(DataChannel channel) {
-        super.attachToChannel(channel);
-
-        if (mergeHandler != null) {
-            mergeHandler.active = false;
-            mergeHandler = null;
-        }
-
-        EventManager eventManager = channel.getEventManager();
-        if (eventManager != null) {
-            mergeHandler = new CayenneContextMergeHandler(this);
-
-            // listen to our channel events...
-            // note that we must reset listener on channel switch, as there is
-            // no
-            // guarantee that a new channel uses the same EventManager.
-            EventUtil.listenForChannelEvents(channel, mergeHandler);
-        }
-    }
-
-    /**
-     * Returns true if this context posts individual object modification events.
-     * Subject used for these events is
-     * <code>ObjectContext.GRAPH_CHANGED_SUBJECT</code>.
-     */
-    public boolean isChangeEventsEnabled() {
-        return graphManager.changeEventsEnabled;
-    }
-
-    /**
-     * Returns true if this context posts lifecycle events. Subjects used for
-     * these events are
-     * <code>ObjectContext.GRAPH_COMMIT_STARTED_SUBJECT, ObjectContext.GRAPH_COMMITTED_SUBJECT,
-     * ObjectContext.GRAPH_COMMIT_ABORTED_SUBJECT, ObjectContext.GRAPH_ROLLEDBACK_SUBJECT.</code>
-     * .
-     */
-    public boolean isLifecycleEventsEnabled() {
-        return graphManager.lifecycleEventsEnabled;
-    }
-
-    @Override
-    public GraphManager getGraphManager() {
-        return graphManager;
-    }
-
-    CayenneContextGraphManager internalGraphManager() {
-        return graphManager;
-    }
-
-    /**
-     * Commits changes to uncommitted objects. First checks if there are changes
-     * in this context and if any changes are detected, sends a commit message
-     * to remote Cayenne service via an internal instance of CayenneConnector.
-     */
-    @Override
-    public void commitChanges() {
-        doCommitChanges(true);
-    }
-
-    GraphDiff doCommitChanges(boolean cascade) {
-
-        int syncType = cascade ? DataChannel.FLUSH_CASCADE_SYNC : DataChannel.FLUSH_NOCASCADE_SYNC;
-
-        GraphDiff commitDiff = null;
-
-        synchronized (graphManager) {
-
-            if (graphManager.hasChanges()) {
-
-                if (isValidatingObjectsOnCommit()) {
-                    ValidationResult result = new ValidationResult();
-                    Iterator<?> it = graphManager.dirtyNodes().iterator();
-                    while (it.hasNext()) {
-                        Persistent p = (Persistent) it.next();
-                        if (p instanceof Validating) {
-                            switch (p.getPersistenceState()) {
-                            case PersistenceState.NEW:
-                                ((Validating) p).validateForInsert(result);
-                                break;
-                            case PersistenceState.MODIFIED:
-                                ((Validating) p).validateForUpdate(result);
-                                break;
-                            case PersistenceState.DELETED:
-                                ((Validating) p).validateForDelete(result);
-                                break;
-                            }
-                        }
-                    }
-
-                    if (result.hasFailures()) {
-                        throw new ValidationException(result);
-                    }
-                }
-
-                graphManager.graphCommitStarted();
-
-                GraphDiff changes = graphManager.getDiffsSinceLastFlush();
-
-                try {
-                    commitDiff = channel.onSync(this, changes, syncType);
-                } catch (Throwable th) {
-                    graphManager.graphCommitAborted();
-
-                    if (th instanceof CayenneRuntimeException) {
-                        throw (CayenneRuntimeException) th;
-                    } else {
-                        throw new CayenneRuntimeException("Commit error", th);
-                    }
-                }
-
-                graphManager.graphCommitted(commitDiff);
-
-                // this event is caught by peer nested ObjectContexts to
-                // synchronize the
-                // state
-                fireDataChannelCommitted(this, changes);
-            }
-        }
-
-        return commitDiff;
-    }
-
-    @Override
-    public void commitChangesToParent() {
-        doCommitChanges(false);
-    }
-
-    @Override
-    public void rollbackChanges() {
-        synchronized (graphManager) {
-            if (graphManager.hasChanges()) {
-
-                GraphDiff diff = graphManager.getDiffs();
-                graphManager.graphReverted();
-
-                channel.onSync(this, diff, DataChannel.ROLLBACK_CASCADE_SYNC);
-                fireDataChannelRolledback(this, diff);
-            }
-        }
-    }
-
-    @Override
-    public void rollbackChangesLocally() {
-        synchronized (graphManager) {
-            if (graphManager.hasChanges()) {
-                GraphDiff diff = graphManager.getDiffs();
-                graphManager.graphReverted();
-
-                fireDataChannelRolledback(this, diff);
-            }
-        }
-    }
-
-    /**
-     * Creates and registers a new Persistent object instance.
-     */
-    @Override
-    public <T> T newObject(Class<T> persistentClass) {
-        if (persistentClass == null) {
-            throw new NullPointerException("Persistent class can't be null.");
-        }
-
-        ObjEntity entity = getEntityResolver().getObjEntity(persistentClass);
-        if (entity == null) {
-            throw new CayenneRuntimeException("No entity mapped for class: %s", persistentClass);
-        }
-
-        ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName());
-        @SuppressWarnings("unchecked")
-        T object = (T) descriptor.createObject();
-        descriptor.injectValueHolders(object);
-        registerNewObject((Persistent) object, entity.getName(), descriptor);
-        return object;
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public void registerNewObject(Object object) {
-        if (object == null) {
-            throw new NullPointerException("An attempt to register null object.");
-        }
-
-        ObjEntity entity = getEntityResolver().getObjEntity(object.getClass());
-        ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName());
-        registerNewObject((Persistent) object, entity.getName(), descriptor);
-    }
-
-    /**
-     * Runs a query, returning result as list.
-     */
-    @Override
-    public List performQuery(Query query) {
-        List result = onQuery(this, query).firstList();
-        return result != null ? result : new ArrayList<>(1);
-    }
-
-    @Override
-    public QueryResponse performGenericQuery(Query query) {
-        return onQuery(this, query);
-    }
-
-    public QueryResponse onQuery(ObjectContext context, Query query) {
-        return new CayenneContextQueryAction(this, context, query).execute();
-    }
-
-    @Override
-    public Collection<?> uncommittedObjects() {
-        synchronized (graphManager) {
-            return graphManager.dirtyNodes();
-        }
-    }
-
-    @Override
-    public Collection<?> deletedObjects() {
-        synchronized (graphManager) {
-            return graphManager.dirtyNodes(PersistenceState.DELETED);
-        }
-    }
-
-    @Override
-    public Collection<?> modifiedObjects() {
-        synchronized (graphManager) {
-            return graphManager.dirtyNodes(PersistenceState.MODIFIED);
-        }
-    }
-
-    @Override
-    public Collection<?> newObjects() {
-        synchronized (graphManager) {
-            return graphManager.dirtyNodes(PersistenceState.NEW);
-        }
-    }
-
-    // ****** non-public methods ******
-
-    void registerNewObject(Persistent object, String entityName, ClassDescriptor descriptor) {
-        /**
-         * We should create new id only if it is not set for this object. It
-         * could have been created, for instance, in child context
-         */
-        ObjectId id = object.getObjectId();
-        if (id == null) {
-            id = new ObjectId(entityName);
-            object.setObjectId(id);
-        }
-
-        injectInitialValue(object);
-    }
-
-    Persistent createFault(ObjectId id) {
-        ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName());
-
-        Persistent object = (Persistent) descriptor.createObject();
-
-        object.setPersistenceState(PersistenceState.HOLLOW);
-        object.setObjectContext(this);
-        object.setObjectId(id);
-
-        graphManager.registerNode(id, object);
-
-        return object;
-    }
-
-    @Override
-    protected GraphDiff onContextFlush(ObjectContext originatingContext, GraphDiff changes, boolean cascade) {
-
-        boolean childContext = this != originatingContext && changes != null;
-
-        if (childContext) {
-
-            // PropertyChangeProcessingStrategy oldStrategy =
-            // getPropertyChangeProcessingStrategy();
-            // setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy.RECORD);
-            try {
-                changes.apply(new CayenneContextChildDiffLoader(this));
-            } finally {
-                // setPropertyChangeProcessingStrategy(oldStrategy);
-            }
-
-            fireDataChannelChanged(originatingContext, changes);
-        }
-
-        return (cascade) ? doCommitChanges(true) : new CompoundDiff();
-    }
-
-    /**
-     * Returns <code>true</code> if there are any modified, deleted or new
-     * objects registered with this CayenneContext, <code>false</code>
-     * otherwise.
-     */
-    public boolean hasChanges() {
-        return graphManager.hasChanges();
-    }
-
-    /**
-     * This method simply returns an iterator over a list of selected objects.
-     * There's no performance benefit of using it vs. regular "select".
-     * 
-     * @since 4.0
-     */
-    public <T> ResultIterator<T> iterator(org.apache.cayenne.query.Select<T> query) {
-        List<T> objects = select(query);
-        return new CollectionResultIterator<T>(objects);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
deleted file mode 100644
index 151b72e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*****************************************************************
- *   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;
-
-import org.apache.cayenne.graph.ChildDiffLoader;
-import org.apache.cayenne.reflect.ArcProperty;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
-
-/**
- * Used for loading child's CayenneContext changes to parent context.
- * 
- * @since 3.0
- */
-class CayenneContextChildDiffLoader extends ChildDiffLoader {
-
-    public CayenneContextChildDiffLoader(CayenneContext context) {
-        super(context);
-    }
-
-    @Override
-    public void nodePropertyChanged(
-            Object nodeId,
-            String property,
-            Object oldValue,
-            Object newValue) {
-
-        super.nodePropertyChanged(nodeId, property, oldValue, newValue);
-
-        Persistent object = (Persistent) context.getGraphManager().getNode(nodeId);
-        context.propertyChanged(object, property, oldValue, newValue);
-    }
-
-    @Override
-    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
-
-        final Persistent source = findObject(nodeId);
-        final Persistent target = findObject(targetNodeId);
-
-        // if a target was later deleted, the diff for arcCreated is still preserved and
-        // can result in NULL target here.
-        if (target == null) {
-            return;
-        }
-
-        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
-                ((ObjectId) nodeId).getEntityName());
-        ArcProperty property = (ArcProperty) descriptor.getProperty(arcId.toString());
-
-        property.visit(new PropertyVisitor() {
-
-            public boolean visitAttribute(AttributeProperty property) {
-                return false;
-            }
-
-            public boolean visitToMany(ToManyProperty property) {
-                property.addTargetDirectly(source, target);
-                return false;
-            }
-
-            public boolean visitToOne(ToOneProperty property) {
-                property.setTarget(source, target, false);
-                return false;
-            }
-        });
-        context.propertyChanged(source, (String) arcId, null, target);
-    }
-
-    @Override
-    public void arcDeleted(Object nodeId, final Object targetNodeId, Object arcId) {
-        final Persistent source = findObject(nodeId);
-
-        // needed as sometime temporary objects are evoked from the context before
-        // changing their relationships
-        if (source == null) {
-            return;
-        }
-
-        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
-                ((ObjectId) nodeId).getEntityName());
-        PropertyDescriptor property = descriptor.getProperty(arcId.toString());
-
-        final Persistent[] target = new Persistent[1];
-        target[0] = findObject(targetNodeId);
-        
-        property.visit(new PropertyVisitor() {
-
-            public boolean visitAttribute(AttributeProperty property) {
-                return false;
-            }
-
-            public boolean visitToMany(ToManyProperty property) {
-                if (target[0] == null) {
-
-                    // this is usually the case when a NEW object was deleted and then
-                    // its relationships were manipulated; so try to locate the object
-                    // in the collection ... the performance of this is rather dubious
-                    // of course...
-                    target[0] = findObjectInCollection(targetNodeId, property
-                            .readProperty(source));
-                }
-
-                if (target[0] == null) {
-                    // ignore?
-                }
-                else {
-                    property.removeTargetDirectly(source, target[0]);
-                }
-
-                return false;
-            }
-
-            public boolean visitToOne(ToOneProperty property) {
-                property.setTarget(source, null, false);
-                return false;
-            }
-        });
-
-        context.propertyChanged(source, (String) arcId, target[0], null);
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java b/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
deleted file mode 100644
index f4865c0..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*****************************************************************
- *   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;
-
-import org.apache.cayenne.event.EventManager;
-import org.apache.cayenne.event.EventSubject;
-import org.apache.cayenne.graph.ArcCreateOperation;
-import org.apache.cayenne.graph.ArcDeleteOperation;
-import org.apache.cayenne.graph.GraphChangeHandler;
-import org.apache.cayenne.graph.GraphDiff;
-import org.apache.cayenne.graph.GraphEvent;
-import org.apache.cayenne.graph.GraphMap;
-import org.apache.cayenne.graph.NodeCreateOperation;
-import org.apache.cayenne.graph.NodeDeleteOperation;
-import org.apache.cayenne.graph.NodeIdChangeOperation;
-import org.apache.cayenne.graph.NodePropertyChangeOperation;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.reflect.ArcProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyException;
-import org.apache.cayenne.reflect.ToManyMapProperty;
-import org.apache.cayenne.util.PersistentObjectMap;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * A GraphMap extension that works together with {@link ObjectContext} to track persistent object
- * changes and send events.
- * 
- * @since 1.2
- */
-final class CayenneContextGraphManager extends GraphMap {
-
-    static final String COMMIT_MARKER = "commit";
-    static final String FLUSH_MARKER = "flush";
-
-    CayenneContext context;
-    Collection<Object> deadIds;
-    boolean changeEventsEnabled;
-    boolean lifecycleEventsEnabled;
-
-    ObjectContextStateLog stateLog;
-    ObjectContextChangeLog changeLog;
-
-    CayenneContextGraphManager(CayenneContext context, boolean changeEventsEnabled,
-            boolean lifecycleEventsEnabled) {
-
-        this.context = context;
-        this.changeEventsEnabled = changeEventsEnabled;
-        this.lifecycleEventsEnabled = lifecycleEventsEnabled;
-
-        this.stateLog = new ObjectContextStateLog(this);
-        this.changeLog = new ObjectContextChangeLog();
-    }
-
-    boolean hasChanges() {
-        return changeLog.size() > 0;
-    }
-
-    boolean hasChangesSinceLastFlush() {
-        int size = changeLog.hasMarker(FLUSH_MARKER) ? changeLog
-                .sizeAfterMarker(FLUSH_MARKER) : changeLog.size();
-        return size > 0;
-    }
-
-    GraphDiff getDiffs() {
-        return changeLog.getDiffs();
-    }
-
-    GraphDiff getDiffsSinceLastFlush() {
-        return changeLog.hasMarker(FLUSH_MARKER) ? changeLog
-                .getDiffsAfterMarker(FLUSH_MARKER) : changeLog.getDiffs();
-    }
-
-    Collection<Object> dirtyNodes() {
-        return stateLog.dirtyNodes();
-    }
-
-    Collection<Object> dirtyNodes(int state) {
-        return stateLog.dirtyNodes(state);
-    }
-
-    @Override
-    public synchronized Object unregisterNode(Object nodeId) {
-        Object node = super.unregisterNode(nodeId);
-
-        // remove node from other collections...
-        if (node != null) {
-            stateLog.unregisterNode(nodeId);
-            changeLog.unregisterNode(nodeId);
-            Persistent object = (Persistent)node;
-            object.setObjectContext(null);
-            object.setPersistenceState(PersistenceState.TRANSIENT);
-            return node;
-        }
-
-        return null;
-    }
-
-    // ****** Sync Events API *****
-    /**
-     * Clears commit marker, but keeps all recorded operations.
-     */
-    void graphCommitAborted() {
-        changeLog.removeMarker(COMMIT_MARKER);
-    }
-
-    /**
-     * Sets commit start marker in the change log. If events are enabled, posts commit
-     * start event.
-     */
-    void graphCommitStarted() {
-        changeLog.setMarker(COMMIT_MARKER);
-    }
-
-    void graphCommitted(GraphDiff parentSyncDiff) {
-        if (parentSyncDiff != null) {
-            new CayenneContextMergeHandler(context).merge(parentSyncDiff);
-        }
-
-        remapTargets();
-
-        stateLog.graphCommitted();
-        reset();
-
-        if (lifecycleEventsEnabled) {
-            // include all diffs after the commit start marker.
-            // We fire event as if it was posted by parent channel, so that
-            // nested contexts could catch it
-            context.fireDataChannelCommitted(context.getChannel(), parentSyncDiff);
-        }
-    }
-
-    /**
-     * Remaps keys in to-many map relationships that contain dirty objects with
-     * potentially modified properties.
-     */
-    private void remapTargets() {
-
-        Iterator<Object> it = stateLog.dirtyIds().iterator();
-
-        EntityResolver resolver = context.getEntityResolver();
-
-        while (it.hasNext()) {
-            ObjectId id = (ObjectId) it.next();
-            ClassDescriptor descriptor = resolver.getClassDescriptor(id.getEntityName());
-
-            Collection<ArcProperty> mapArcProperties = descriptor.getMapArcProperties();
-            if (!mapArcProperties.isEmpty()) {
-
-                Object object = getNode(id);
-
-                for (ArcProperty arc : mapArcProperties) {
-                    ToManyMapProperty reverseArc = (ToManyMapProperty) arc
-                            .getComplimentaryReverseArc();
-
-                    Object source = arc.readPropertyDirectly(object);
-                    if (source != null && !reverseArc.isFault(source)) {
-                        remapTarget(reverseArc, source, object);
-                    }
-                }
-            }
-        }
-    }
-
-    // clone of DataDomainSyncBucket.remapTarget
-    private final void remapTarget(
-            ToManyMapProperty property,
-            Object source,
-            Object target) throws PropertyException {
-
-        @SuppressWarnings("unchecked")
-        Map<Object, Object> map = (Map<Object, Object>) property.readProperty(source);
-
-        Object newKey = property.getMapKey(target);
-        Object currentValue = map.get(newKey);
-
-        if (currentValue == target) {
-            // nothing to do
-            return;
-        }
-        // else - do not check for conflicts here (i.e. another object mapped for the same
-        // key), as we have no control of the order in which this method is called, so
-        // another object may be remapped later by the caller
-
-        // must do a slow map scan to ensure the object is not mapped under a different
-        // key...
-        Iterator<?> it = map.entrySet().iterator();
-        while (it.hasNext()) {
-            Map.Entry<?, ?> e = (Map.Entry<?, ?>) it.next();
-            if (e.getValue() == target) {
-                // this remove does not trigger event in PersistentObjectMap
-                it.remove();
-                break;
-            }
-        }
-
-        // TODO: (andrey, 25/11/09 - this is a hack to prevent event triggering
-        // (and concurrent exceptions)
-        // should find a way to get rid of type casting
-        ((PersistentObjectMap) map).putDirectly(newKey, target);
-    }
-
-    void graphFlushed() {
-        changeLog.setMarker(FLUSH_MARKER);
-    }
-
-    void graphReverted() {
-        GraphDiff diff = changeLog.getDiffs();
-
-        diff.undo(new RollbackChangeHandler());
-        stateLog.graphReverted();
-        reset();
-
-        if (lifecycleEventsEnabled) {
-            context.fireDataChannelRolledback(context, diff);
-        }
-    }
-
-    // ****** GraphChangeHandler API ******
-    // =====================================================
-
-    @Override
-    public synchronized void nodeIdChanged(Object nodeId, Object newId) {
-        stateLog.nodeIdChanged(nodeId, newId);
-        processChange(new NodeIdChangeOperation(nodeId, newId));
-    }
-
-    @Override
-    public synchronized void nodeCreated(Object nodeId) {
-        stateLog.nodeCreated(nodeId);
-        processChange(new NodeCreateOperation(nodeId));
-    }
-
-    @Override
-    public synchronized void nodeRemoved(Object nodeId) {
-        stateLog.nodeRemoved(nodeId);
-        processChange(new NodeDeleteOperation(nodeId));
-    }
-
-    @Override
-    public synchronized void nodePropertyChanged(
-            Object nodeId,
-            String property,
-            Object oldValue,
-            Object newValue) {
-
-        stateLog.nodePropertyChanged(nodeId, property, oldValue, newValue);
-        processChange(new NodePropertyChangeOperation(
-                nodeId,
-                property,
-                oldValue,
-                newValue));
-    }
-
-    @Override
-    public synchronized void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
-        stateLog.arcCreated(nodeId, targetNodeId, arcId);
-        processChange(new ArcCreateOperation(nodeId, targetNodeId, arcId));
-    }
-
-    @Override
-    public synchronized void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
-        stateLog.arcDeleted(nodeId, targetNodeId, arcId);
-        processChange(new ArcDeleteOperation(nodeId, targetNodeId, arcId));
-    }
-
-    // ****** helper methods ******
-    // =====================================================
-
-    private void processChange(GraphDiff diff) {
-        changeLog.addOperation(diff);
-
-        if (changeEventsEnabled) {
-            context.fireDataChannelChanged(context, diff);
-        }
-    }
-
-    /**
-     * Wraps GraphDiff in a GraphEvent and sends it via EventManager with specified
-     * subject.
-     */
-    void send(GraphDiff diff, EventSubject subject, Object eventSource) {
-        EventManager manager = (context.getChannel() != null) ? context
-                .getChannel()
-                .getEventManager() : null;
-
-        if (manager != null) {
-            GraphEvent e = new GraphEvent(context, eventSource, diff);
-            manager.postEvent(e, subject);
-        }
-    }
-
-    void reset() {
-        changeLog.reset();
-
-        if (deadIds != null) {
-            // unregister dead ids...
-            for (final Object deadId : deadIds) {
-                nodes.remove(deadId);
-            }
-
-            deadIds = null;
-        }
-    }
-
-    Collection<Object> deadIds() {
-        if (deadIds == null) {
-            deadIds = new ArrayList<>();
-        }
-
-        return deadIds;
-    }
-
-    /**
-     * This change handler is used to perform rollback actions for Cayenne context
-     */
-    class RollbackChangeHandler implements GraphChangeHandler {
-
-        public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
-            context.mergeHandler.arcCreated(nodeId, targetNodeId, arcId);
-            CayenneContextGraphManager.this.arcCreated(nodeId, targetNodeId, arcId);
-        }
-
-        public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
-            context.mergeHandler.arcDeleted(nodeId, targetNodeId, arcId);
-            CayenneContextGraphManager.this.arcDeleted(nodeId, targetNodeId, arcId);
-        }
-
-        public void nodeCreated(Object nodeId) {
-            CayenneContextGraphManager.this.nodeCreated(nodeId);
-        }
-
-        public void nodeIdChanged(Object nodeId, Object newId) {
-            CayenneContextGraphManager.this.nodeIdChanged(nodeId, newId);
-        }
-
-        /**
-         * Need to write property directly to this context
-         */
-        public void nodePropertyChanged(
-                Object nodeId,
-                String property,
-                Object oldValue,
-                Object newValue) {
-            context.mergeHandler
-                    .nodePropertyChanged(nodeId, property, oldValue, newValue);
-            CayenneContextGraphManager.this.nodePropertyChanged(
-                    nodeId,
-                    property,
-                    oldValue,
-                    newValue);
-        }
-
-        public void nodeRemoved(Object nodeId) {
-            CayenneContextGraphManager.this.nodeRemoved(nodeId);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java b/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
deleted file mode 100644
index f490128..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*****************************************************************
- *   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;
-
-import org.apache.cayenne.graph.GraphChangeHandler;
-import org.apache.cayenne.graph.GraphDiff;
-import org.apache.cayenne.graph.GraphEvent;
-import org.apache.cayenne.reflect.ArcProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyDescriptor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.util.Util;
-
-/**
- * An object that merges "backdoor" modifications of the object graph coming from the
- * underlying DataChannel. When doing an update, CayenneContextMergeHandler blocks
- * broadcasting of GraphManager events.
- * 
- * @since 1.2
- */
-class CayenneContextMergeHandler implements GraphChangeHandler, DataChannelListener {
-
-    CayenneContext context;
-    boolean active;
-
-    CayenneContextMergeHandler(CayenneContext context) {
-        this.context = context;
-        this.active = true;
-    }
-
-    // ******* DataChannelListener methods *******
-
-    public void graphChanged(final GraphEvent e) {
-        // process flush
-        if (shouldProcessEvent(e) && e.getDiff() != null) {
-            runWithEventsDisabled(new Runnable() {
-
-                public void run() {
-                    e.getDiff().apply(CayenneContextMergeHandler.this);
-
-                }
-            });
-
-            // post event outside of "execute" to make sure it is sent
-            repostAfterMerge(e);
-        }
-    }
-
-    public void graphFlushed(final GraphEvent e) {
-        // TODO (Andrus, 10/17/2005) - there are a few problems with commit processing:
-
-        // 1. Event mechanism reliability:
-        // - events may come out of order (commit and then preceeding flush)
-        // - events may be missing all together (commit arrived, while prior flush did
-        // not)
-        // Possible solution - an "event_version_id" to be used for optimistic locking
-
-        // 2. We don't know if our own dirty objects were committed or not...
-        // For now we will simply merge the changes, and keep the context dirty
-
-        if (shouldProcessEvent(e)) {
-
-            runWithEventsDisabled(new Runnable() {
-
-                public void run() {
-
-                    if (e.getDiff() != null) {
-                        e.getDiff().apply(CayenneContextMergeHandler.this);
-                    }
-                }
-            });
-
-            // post event outside of "execute" to make sure it is sent
-            repostAfterMerge(e);
-        }
-    }
-
-    public void graphRolledback(final GraphEvent e) {
-
-        // TODO: andrus, 3/29/2007: per CAY-771, if a LOCAL peer context posted the event,
-        // just ignore it, however if the REMOTE peer reverted the parent remote
-        // DataContext, we need to invalidate stale committed objects...
-    }
-
-    // ******* End DataChannelListener methods *******
-
-    void repostAfterMerge(GraphEvent originalEvent) {
-        // though the subject is CHANGE, "merge" events are really lifecycle.
-        if (context.isLifecycleEventsEnabled()) {
-            context.fireDataChannelChanged(originalEvent.getSource(), originalEvent.getDiff());
-        }
-    }
-
-    /**
-     * Executes merging of the external diff.
-     */
-    void merge(final GraphDiff diff) {
-        runWithEventsDisabled(new Runnable() {
-
-            public void run() {
-                diff.apply(CayenneContextMergeHandler.this);
-            }
-        });
-    }
-
-    // ******* GraphChangeHandler methods *********
-
-    public void nodeIdChanged(Object nodeId, Object newId) {
-        // do not unregister the node just yet... only put replaced id in deadIds to
-        // remove it later. Otherwise stored operations will not work
-        Object node = context.internalGraphManager().getNode(nodeId);
-
-        if (node != null) {
-            context.internalGraphManager().deadIds().add(nodeId);
-            context.internalGraphManager().registerNode(newId, node);
-
-            if (node instanceof Persistent) {
-                // inject new id
-                ((Persistent) node).setObjectId((ObjectId) newId);
-            }
-        }
-    }
-
-    public void nodeCreated(Object nodeId) {
-        // ignore
-    }
-
-    public void nodeRemoved(Object nodeId) {
-        context.getGraphManager().unregisterNode(nodeId);
-    }
-
-    public void nodePropertyChanged(
-            Object nodeId,
-            String property,
-            Object oldValue,
-            Object newValue) {
-
-        Object object = context.internalGraphManager().getNode(nodeId);
-        if (object != null) {
-
-            // do not override local changes....
-            PropertyDescriptor p = propertyForId(nodeId, property);
-            if (Util.nullSafeEquals(p.readPropertyDirectly(object), oldValue)) {
-
-                p.writePropertyDirectly(object, oldValue, newValue);
-            }
-        }
-    }
-
-    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
-        // null source or target likely means the object is not faulted yet... Faults
-        // shouldn't get disturbed by adding/removing arcs
-
-        Object source = context.internalGraphManager().getNode(nodeId);
-        if (source == null) {
-            // no need to connect non-existent object
-            return;
-        }
-
-        // TODO (Andrus, 10/17/2005) - check for local modifications to avoid
-        // overwriting...
-
-        ArcProperty p = (ArcProperty) propertyForId(nodeId, arcId.toString());
-        if (p.isFault(source)) {
-            return;
-        }
-
-        Object target = context.internalGraphManager().getNode(targetNodeId);
-        if (target == null) {
-            target = context.createFault((ObjectId) targetNodeId);
-        }
-
-        try {
-            if (p instanceof ToManyProperty) {
-                ((ToManyProperty) p).addTargetDirectly(source, target);
-            }
-            else {
-                p.writePropertyDirectly(source, null, target);
-            }
-        }
-        finally {
-        }
-    }
-
-    public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
-
-        // null source or target likely means the object is not faulted yet... Faults
-        // shouldn't get disturbed by adding/removing arcs
-
-        Object source = context.internalGraphManager().getNode(nodeId);
-        if (source == null) {
-            // no need to disconnect non-existent object
-            return;
-        }
-
-        // (see "TODO" in 'arcCreated')
-        ArcProperty p = (ArcProperty) propertyForId(nodeId, arcId.toString());
-        if (p.isFault(source)) {
-            return;
-        }
-
-        Object target = context.internalGraphManager().getNode(targetNodeId);
-        if (target == null) {
-            target = context.createFault((ObjectId) targetNodeId);
-        }
-
-        try {
-            if (p instanceof ToManyProperty) {
-                ((ToManyProperty) p).removeTargetDirectly(source, target);
-            }
-            else {
-                p.writePropertyDirectly(source, target, null);
-            }
-        }
-        finally {
-        }
-    }
-
-    private PropertyDescriptor propertyForId(Object nodeId, String propertyName) {
-        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
-                ((ObjectId) nodeId).getEntityName());
-        return descriptor.getProperty(propertyName);
-    }
-
-    // Returns true if this object is active; an event came from our channel, but did not
-    // originate in it.
-    boolean shouldProcessEvent(GraphEvent e) {
-        // only process events that came from our channel, but did not originate in it
-        // (i.e. likely posted by EventBridge)
-        return active
-                && e.getSource() == context.getChannel()
-                && e.getPostedBy() != context
-                && e.getPostedBy() != context.getChannel();
-    }
-
-    // executes a closure, disabling ObjectContext events for the duration of the
-    // execution.
-
-    private void runWithEventsDisabled(Runnable closure) {
-
-        synchronized (context.internalGraphManager()) {
-            boolean changeEventsEnabled = context.internalGraphManager().changeEventsEnabled;
-            context.internalGraphManager().changeEventsEnabled = false;
-
-            boolean lifecycleEventsEnabled = context.internalGraphManager().lifecycleEventsEnabled;
-            context.internalGraphManager().lifecycleEventsEnabled = false;
-
-            try {
-                closure.run();
-            }
-            finally {
-                context.internalGraphManager().changeEventsEnabled = changeEventsEnabled;
-                context.internalGraphManager().lifecycleEventsEnabled = lifecycleEventsEnabled;
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java
deleted file mode 100644
index e0b693c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*****************************************************************
- *   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;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.cayenne.cache.QueryCacheEntryFactory;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.RefreshQuery;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
-import org.apache.cayenne.remote.RemoteIncrementalFaultList;
-import org.apache.cayenne.util.ListResponse;
-import org.apache.cayenne.util.ObjectContextQueryAction;
-
-/**
- * @since 1.2
- */
-class CayenneContextQueryAction extends ObjectContextQueryAction {
-
-    CayenneContextQueryAction(CayenneContext actingContext, ObjectContext targetContext,
-            Query query) {
-        super(actingContext, targetContext, query);
-    }
-
-    @Override
-    protected boolean interceptPaginatedQuery() {
-        if (metadata.getPageSize() > 0) {
-            response = new ListResponse(new RemoteIncrementalFaultList(
-                    actingContext,
-                    query));
-            return DONE;
-        }
-
-        return !DONE;
-    }
-
-    @Override
-    protected QueryCacheEntryFactory getCacheObjectFactory() {
-        return new QueryCacheEntryFactory() {
-
-            public List createObject() {
-                if (interceptPaginatedQuery() != DONE) {
-                    runQuery();
-                }
-                return response.firstList();
-            }
-        };
-    }
-
-    @Override
-    protected boolean interceptRefreshQuery() {
-        if (query instanceof RefreshQuery) {
-            RefreshQuery refreshQuery = (RefreshQuery) query;
-
-            CayenneContext context = (CayenneContext) actingContext;
-
-            // handle 4 separate scenarios, but do not combine them as it will be
-            // unclear how to handle cascading behavior
-
-            // 1. refresh all
-            if (refreshQuery.isRefreshAll()) {
-
-                invalidateLocally(context.internalGraphManager(), context
-                        .internalGraphManager()
-                        .registeredNodes()
-                        .iterator());
-                context.getQueryCache().clear();
-
-                // cascade
-                return !DONE;
-            }
-
-            // 2. invalidate object collection
-            Collection<?> objects = refreshQuery.getObjects();
-            if (objects != null && !objects.isEmpty()) {
-
-                invalidateLocally(context.internalGraphManager(), objects.iterator());
-
-                // cascade
-                return !DONE;
-            }
-
-            // 3. refresh query - have to do it eagerly to refresh the objects involved
-            if (refreshQuery.getQuery() != null) {
-                Query cachedQuery = refreshQuery.getQuery();
-
-                String cacheKey = cachedQuery
-                        .getMetaData(context.getEntityResolver())
-                        .getCacheKey();
-                context.getQueryCache().remove(cacheKey);
-
-                this.response = context.performGenericQuery(cachedQuery);
-
-                // do not cascade to avoid running query twice
-                return DONE;
-            }
-
-            // 4. refresh groups...
-            if (refreshQuery.getGroupKeys() != null
-                    && refreshQuery.getGroupKeys().length > 0) {
-
-                String[] groups = refreshQuery.getGroupKeys();
-                for (String group : groups) {
-                    context.getQueryCache().removeGroup(group);
-                }
-
-                // cascade group invalidation
-                return !DONE;
-            }
-        }
-
-        return !DONE;
-    }
-
-    private void invalidateLocally(CayenneContextGraphManager graphManager, Iterator<?> it) {
-        if (!it.hasNext()) {
-            return;
-        }
-
-        EntityResolver resolver = actingContext.getEntityResolver();
-
-        while (it.hasNext()) {
-            final Persistent object = (Persistent) it.next();
-
-            // we don't care about NEW objects,
-            // but we still do care about HOLLOW, since snapshot might still be
-            // present
-            if (object.getPersistenceState() == PersistenceState.NEW) {
-                continue;
-            }
-
-            ObjectId id = object.getObjectId();
-
-            // per CAY-1082 ROP objects (unlike CayenneDataObject) require all
-            // relationship faults invalidation.
-            ClassDescriptor descriptor = resolver.getClassDescriptor(id.getEntityName());
-            PropertyVisitor arcInvalidator = new PropertyVisitor() {
-
-                public boolean visitAttribute(AttributeProperty property) {
-                    return true;
-                }
-
-                public boolean visitToMany(ToManyProperty property) {
-                    property.invalidate(object);
-                    return true;
-                }
-
-                public boolean visitToOne(ToOneProperty property) {
-                    property.invalidate(object);
-                    return true;
-                }
-            };
-
-            descriptor.visitProperties(arcInvalidator);
-            object.setPersistenceState(PersistenceState.HOLLOW);
-            
-            // remove cached changes
-            graphManager.changeLog.unregisterNode(id);
-            graphManager.stateLog.unregisterNode(id);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java
deleted file mode 100644
index 26f2f1b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-/**
- * A message sent to a remote service to request Cayenne mapping info.
- * 
- * @since 1.2
- */
-public class BootstrapMessage implements ClientMessage {
-
-    /**
-     * Returns a description of the type of message. In this case always "Bootstrap".
-     */
-    @Override
-    public String toString() {
-        return "Bootstrap";
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java
deleted file mode 100644
index e0740b3..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import java.io.Serializable;
-
-/**
- * A tag interface representing a message sent by a remote client to Cayenne service.
- * 
- * @since 1.2
- */
-public interface ClientMessage extends Serializable {
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
deleted file mode 100644
index 51c7946..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.query.QueryMetadataProxy;
-import org.apache.cayenne.query.QueryRouter;
-import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SQLActionVisitor;
-
-/**
- * A client wrapper for the incremental query that overrides the metadata to ensure that
- * query result is cached on the server, so that subranges could be retrieved at a later
- * time.
- * 
- * @since 1.2
- */
-class IncrementalQuery implements Query {
-
-    private Query query;
-    private String cacheKey;
-
-    IncrementalQuery(Query query, String cacheKey) {
-        this.query = query;
-        this.cacheKey = cacheKey;
-    }
-
-    public QueryMetadata getMetaData(EntityResolver resolver) {
-        final QueryMetadata metadata = query.getMetaData(resolver);
-
-        // the way paginated queries work on the server is that they are never cached
-        // (IncrementalFaultList interception happens before cache interception). So
-        // overriding caching settings in the metadata will only affect
-        // ClientServerChannel behavior
-        return new QueryMetadataProxy(metadata) {
-            public Query getOriginatingQuery() {
-                return null;
-            }
-
-            public String getCacheKey() {
-                return cacheKey;
-            }
-        };
-    }
-
-    public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
-        query.route(router, resolver, substitutedQuery);
-    }
-
-    public SQLAction createSQLAction(SQLActionVisitor visitor) {
-        return query.createSQLAction(visitor);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
deleted file mode 100644
index 9b6f563..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.ResultBatchIterator;
-import org.apache.cayenne.ResultIterator;
-import org.apache.cayenne.ResultIteratorCallback;
-import org.apache.cayenne.access.IncrementalFaultList;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Ordering;
-import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.query.QueryMetadataProxy;
-import org.apache.cayenne.query.QueryRouter;
-import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SQLActionVisitor;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.query.SortOrder;
-import org.apache.cayenne.util.XMLEncoder;
-
-/**
- * A SelectQuery decorator that overrides the metadata to ensure that query
- * result is cached on the server, so that subranges could be retrieved at a
- * later time. Note that a special decorator that is a subclass of SelectQuery
- * is needed so that {@link IncrementalFaultList} on the server-side could apply
- * SelectQuery-specific optimizations.
- * 
- * @since 3.0
- */
-class IncrementalSelectQuery<T> extends SelectQuery<T> {
-
-	private SelectQuery<T> query;
-	private String cacheKey;
-
-	IncrementalSelectQuery(SelectQuery<T> delegate, String cacheKey) {
-		this.query = delegate;
-		this.cacheKey = cacheKey;
-	}
-
-	@Override
-	public QueryMetadata getMetaData(EntityResolver resolver) {
-		final QueryMetadata metadata = query.getMetaData(resolver);
-
-		// the way paginated queries work on the server is that they are never
-		// cached
-		// (IncrementalFaultList interception happens before cache
-		// interception). So
-		// overriding caching settings in the metadata will only affect
-		// ClientServerChannel behavior
-		return new QueryMetadataProxy(metadata) {
-			public Query getOriginatingQuery() {
-				return null;
-			}
-
-			public String getCacheKey() {
-				return cacheKey;
-			}
-		};
-	}
-
-	@Override
-	public void addOrdering(Ordering ordering) {
-		query.addOrdering(ordering);
-	}
-
-	@Override
-	public void addOrdering(String sortPathSpec, SortOrder order) {
-		query.addOrdering(sortPathSpec, order);
-	}
-
-	@Override
-	public void addOrderings(Collection<? extends Ordering> orderings) {
-		query.addOrderings(orderings);
-	}
-
-	@Override
-	public PrefetchTreeNode addPrefetch(String prefetchPath) {
-		return query.addPrefetch(prefetchPath);
-	}
-
-	@Override
-	public void andQualifier(Expression e) {
-		query.andQualifier(e);
-	}
-
-	@Override
-	public void clearOrderings() {
-		query.clearOrderings();
-	}
-
-	@Override
-	public void clearPrefetches() {
-		query.clearPrefetches();
-	}
-
-	@Override
-	public SelectQuery<T> createQuery(Map<String, ?> parameters) {
-		return query.createQuery(parameters);
-	}
-
-	@Override
-	public SQLAction createSQLAction(SQLActionVisitor visitor) {
-		return query.createSQLAction(visitor);
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		return query.equals(obj);
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	public String getCacheGroup() {
-		return super.getCacheGroup();
-	}
-
-	@Override
-	public int getFetchLimit() {
-		return query.getFetchLimit();
-	}
-
-	@Override
-	public List<Ordering> getOrderings() {
-		return query.getOrderings();
-	}
-
-	@Override
-	public int getPageSize() {
-		return query.getPageSize();
-	}
-
-	@Override
-	public PrefetchTreeNode getPrefetchTree() {
-		return query.getPrefetchTree();
-	}
-
-	@Override
-	public Expression getQualifier() {
-		return query.getQualifier();
-	}
-
-	@Override
-	public Object getRoot() {
-		return query.getRoot();
-	}
-
-	@Override
-	public int hashCode() {
-		return query.hashCode();
-	}
-
-	@Override
-	public void initWithProperties(Map<String, ?> properties) {
-		query.initWithProperties(properties);
-	}
-
-	@Override
-	public boolean isDistinct() {
-		return query.isDistinct();
-	}
-
-	@Override
-	public boolean isFetchingDataRows() {
-		return query.isFetchingDataRows();
-	}
-
-	@Override
-	public void orQualifier(Expression e) {
-		query.orQualifier(e);
-	}
-
-	@Override
-	public SelectQuery<T> queryWithParameters(Map<String, ?> parameters, boolean pruneMissing) {
-		return query.queryWithParameters(parameters, pruneMissing);
-	}
-
-	@Override
-	public SelectQuery<T> queryWithParameters(Map<String, ?> parameters) {
-		return query.queryWithParameters(parameters);
-	}
-
-	@Override
-	public void removeOrdering(Ordering ordering) {
-		query.removeOrdering(ordering);
-	}
-
-	@Override
-	public void removePrefetch(String prefetchPath) {
-		query.removePrefetch(prefetchPath);
-	}
-
-	@Override
-	public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
-		query.route(router, resolver, substitutedQuery);
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	public void setCacheGroup(String cacheGroup) {
-		query.setCacheGroup(cacheGroup);
-	}
-
-	@Override
-	public void setDistinct(boolean distinct) {
-		query.setDistinct(distinct);
-	}
-
-	@SuppressWarnings("deprecation")
-	@Override
-	public void setFetchingDataRows(boolean flag) {
-		query.setFetchingDataRows(flag);
-	}
-
-	@Override
-	public void setFetchLimit(int fetchLimit) {
-		query.setFetchLimit(fetchLimit);
-	}
-
-	@Override
-	public void setPageSize(int pageSize) {
-		query.setPageSize(pageSize);
-	}
-
-	@Override
-	public void setPrefetchTree(PrefetchTreeNode prefetchTree) {
-		query.setPrefetchTree(prefetchTree);
-	}
-
-	@Override
-	public void setQualifier(Expression qualifier) {
-		query.setQualifier(qualifier);
-	}
-
-	@Override
-	public void setRoot(Object value) {
-		query.setRoot(value);
-	}
-
-	@Override
-	public String toString() {
-		return query.toString();
-	}
-
-	@Override
-	public List<T> select(ObjectContext context) {
-		return query.select(context);
-	}
-
-	@Override
-	public T selectOne(ObjectContext context) {
-		return query.selectOne(context);
-	}
-
-	@Override
-	public T selectFirst(ObjectContext context) {
-		return query.selectFirst(context);
-	}
-
-	@Override
-	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
-		query.iterate(context, callback);
-	}
-
-	@Override
-	public ResultIterator<T> iterator(ObjectContext context) {
-		return query.iterator(context);
-	}
-
-	@Override
-	public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
-		return query.batchIterator(context, size);
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java
deleted file mode 100644
index 3c8b4d5..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import org.apache.cayenne.query.Query;
-
-/**
- * A message passed to a DataChannel to request a query execution with result returned as
- * QueryResponse.
- * 
- * @since 1.2
- */
-public class QueryMessage implements ClientMessage {
-
-    protected Query query;
-
-    // for hessian serialization
-    @SuppressWarnings("unused")
-    private QueryMessage() {
-
-    }
-
-    public QueryMessage(Query query) {
-        this.query = query;
-    }
-
-    public Query getQuery() {
-        return query;
-    }
-
-    /**
-     * Returns a description of the type of message. In this case always "Query".
-     */
-    @Override
-    public String toString() {
-        return "Query";
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
deleted file mode 100644
index f2b5e78..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*****************************************************************
- *   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.remote;
-
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryCacheStrategy;
-import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.query.QueryMetadataProxy;
-import org.apache.cayenne.query.QueryRouter;
-import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SQLActionVisitor;
-import org.apache.cayenne.reflect.ClassDescriptor;
-
-/**
- * A Query that fetches a range of objects from a previously fetched server-side paginated
- * list. This query is client-only and can't be executed on the server.
- * 
- * @since 1.2
- */
-class RangeQuery implements Query {
-
-    private String cacheKey;
-    private int fetchOffset;
-    private int fetchLimit;
-    private Query originatingQuery;
-
-    // exists for hessian serialization.
-    @SuppressWarnings("unused")
-    private RangeQuery() {
-
-    }
-
-    /**
-     * Creates a query that returns a single page from an existing cached server-side
-     * result list.
-     */
-    RangeQuery(String cacheKey, int fetchStartIndex, int fetchLimit,
-            Query originatingQuery) {
-        this.cacheKey = cacheKey;
-        this.fetchOffset = fetchStartIndex;
-        this.fetchLimit = fetchLimit;
-        this.originatingQuery = originatingQuery;
-    }
-
-    public QueryMetadata getMetaData(EntityResolver resolver) {
-        final QueryMetadata originatingMetadata = originatingQuery.getMetaData(resolver);
-
-        return new QueryMetadataProxy(originatingMetadata) {
-
-            public Query getOriginatingQuery() {
-                return originatingQuery;
-            }
-
-            public List<Object> getResultSetMapping() {
-                return null;
-            }
-
-            public boolean isSingleResultSetMapping() {
-                return false;
-            }
-
-            public String getCacheKey() {
-                return cacheKey;
-            }
-
-            public String getCacheGroup() {
-                return null;
-            }
-
-            public int getFetchOffset() {
-                return fetchOffset;
-            }
-
-            public int getFetchLimit() {
-                return fetchLimit;
-            }
-
-            public int getPageSize() {
-                return 0;
-            }
-
-            /**
-             * @since 3.0
-             */
-            public QueryCacheStrategy getCacheStrategy() {
-                return QueryCacheStrategy.getDefaultStrategy();
-            }
-
-            public DataMap getDataMap() {
-                throw new UnsupportedOperationException();
-            }
-
-            public DbEntity getDbEntity() {
-                throw new UnsupportedOperationException();
-            }
-
-            public ObjEntity getObjEntity() {
-                throw new UnsupportedOperationException();
-            }
-
-            public ClassDescriptor getClassDescriptor() {
-                throw new UnsupportedOperationException();
-            }
-
-            public Procedure getProcedure() {
-                throw new UnsupportedOperationException();
-            }
-
-            public Map<String, String> getPathSplitAliases() {
-                throw new UnsupportedOperationException();
-            }
-
-            public boolean isRefreshingObjects() {
-                throw new UnsupportedOperationException();
-            }
-
-            public int getStatementFetchSize() {
-                return 0;
-            }
-        };
-    }
-
-    public SQLAction createSQLAction(SQLActionVisitor visitor) {
-        throw new UnsupportedOperationException();
-    }
-
-    public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
-        throw new UnsupportedOperationException();
-    }
-
-}


[5/5] cayenne git commit: CAY-2373 cayenne-rop-server module - move org.apache.cayenne.remote package to cayenne-rop server module - remove dependencies from cayenne-server pom.xml - update tutorial

Posted by nt...@apache.org.
CAY-2373 cayenne-rop-server module
 - move org.apache.cayenne.remote package to cayenne-rop server module
 - remove dependencies from cayenne-server pom.xml
 - update tutorial


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

Branch: refs/heads/master
Commit: 38f37d79aa6539913b83235fd4fff4ed6f826b85
Parents: eec08b7
Author: Nikita Timofeev <st...@gmail.com>
Authored: Tue Dec 12 15:52:43 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Tue Dec 12 15:52:43 2017 +0300

----------------------------------------------------------------------
 cayenne-rop-server/pom.xml                      |   2 +
 .../java/org/apache/cayenne/CayenneContext.java | 391 +++++++++++
 .../cayenne/CayenneContextChildDiffLoader.java  | 142 ++++
 .../cayenne/CayenneContextGraphManager.java     | 379 +++++++++++
 .../cayenne/CayenneContextMergeHandler.java     | 274 ++++++++
 .../cayenne/CayenneContextQueryAction.java      | 186 ++++++
 .../apache/cayenne/remote/BootstrapMessage.java |  36 +
 .../apache/cayenne/remote/ClientMessage.java    |  31 +
 .../apache/cayenne/remote/IncrementalQuery.java |  73 ++
 .../cayenne/remote/IncrementalSelectQuery.java  | 300 +++++++++
 .../org/apache/cayenne/remote/QueryMessage.java |  55 ++
 .../org/apache/cayenne/remote/RangeQuery.java   | 156 +++++
 .../remote/RemoteIncrementalFaultList.java      | 668 +++++++++++++++++++
 .../apache/cayenne/remote/RemoteService.java    |  54 ++
 .../apache/cayenne/remote/RemoteSession.java    | 145 ++++
 .../org/apache/cayenne/remote/SyncMessage.java  |  91 +++
 .../hessian/CayenneSerializerFactory.java       |  43 ++
 .../cayenne/remote/hessian/HessianConfig.java   | 114 ++++
 .../remote/hessian/service/HessianService.java  |  64 ++
 .../service/ServerDataRowSerializer.java        |  56 ++
 .../ServerPersistentObjectListSerializer.java   |  50 ++
 .../service/ServerSerializerFactory.java        |  71 ++
 .../remote/service/BaseRemoteService.java       | 199 ++++++
 .../cayenne/remote/service/DispatchHelper.java  |  49 ++
 .../remote/service/HttpRemoteService.java       | 134 ++++
 .../remote/service/MissingSessionException.java |  38 ++
 .../cayenne/remote/service/ServerSession.java   |  49 ++
 .../cayenne/CayenneContextGraphManagerTest.java |  67 ++
 .../cayenne/remote/MockRemoteService.java       |  40 ++
 .../cayenne/remote/RemoteSessionTest.java       |  58 ++
 .../remote/hessian/HessianConfigTest.java       |  61 ++
 .../hessian/MockAbstractSerializerFactory.java  |  51 ++
 .../hessian/service/HessianServiceTest.java     |  73 ++
 .../remote/service/BaseRemoteServiceTest.java   | 142 ++++
 .../remote/service/DispatchHelperTest.java      |  54 ++
 .../service/MockUnserializableException.java    |  24 +
 cayenne-server/pom.xml                          |  31 -
 .../java/org/apache/cayenne/CayenneContext.java | 391 -----------
 .../cayenne/CayenneContextChildDiffLoader.java  | 142 ----
 .../cayenne/CayenneContextGraphManager.java     | 379 -----------
 .../cayenne/CayenneContextMergeHandler.java     | 274 --------
 .../cayenne/CayenneContextQueryAction.java      | 186 ------
 .../apache/cayenne/remote/BootstrapMessage.java |  36 -
 .../apache/cayenne/remote/ClientMessage.java    |  31 -
 .../apache/cayenne/remote/IncrementalQuery.java |  73 --
 .../cayenne/remote/IncrementalSelectQuery.java  | 300 ---------
 .../org/apache/cayenne/remote/QueryMessage.java |  55 --
 .../org/apache/cayenne/remote/RangeQuery.java   | 156 -----
 .../remote/RemoteIncrementalFaultList.java      | 668 -------------------
 .../apache/cayenne/remote/RemoteService.java    |  54 --
 .../apache/cayenne/remote/RemoteSession.java    | 145 ----
 .../org/apache/cayenne/remote/SyncMessage.java  |  91 ---
 .../hessian/CayenneSerializerFactory.java       |  43 --
 .../cayenne/remote/hessian/HessianConfig.java   | 114 ----
 .../remote/hessian/service/HessianService.java  |  64 --
 .../service/ServerDataRowSerializer.java        |  56 --
 .../ServerPersistentObjectListSerializer.java   |  50 --
 .../service/ServerSerializerFactory.java        |  71 --
 .../remote/service/BaseRemoteService.java       | 199 ------
 .../cayenne/remote/service/DispatchHelper.java  |  49 --
 .../remote/service/HttpRemoteService.java       | 134 ----
 .../remote/service/MissingSessionException.java |  38 --
 .../cayenne/remote/service/ServerSession.java   |  49 --
 .../cayenne/CayenneContextGraphManagerTest.java |  67 --
 .../cayenne/remote/MockRemoteService.java       |  40 --
 .../cayenne/remote/RemoteSessionTest.java       |  58 --
 .../remote/hessian/HessianConfigTest.java       |  61 --
 .../hessian/MockAbstractSerializerFactory.java  |  51 --
 .../hessian/service/HessianServiceTest.java     |  73 --
 .../remote/service/BaseRemoteServiceTest.java   | 142 ----
 .../remote/service/DispatchHelperTest.java      |  54 --
 .../service/MockUnserializableException.java    |  24 -
 .../tutorial/persistent/client/Main.java        |   2 +-
 tutorials/tutorial-rop-server/pom.xml           |   3 +-
 .../src/main/resources/cayenne-project.xml      |   9 +-
 .../src/main/resources/datamap.map.xml          |  10 +-
 76 files changed, 4433 insertions(+), 4460 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/pom.xml b/cayenne-rop-server/pom.xml
index eddbef1..93399a0 100644
--- a/cayenne-rop-server/pom.xml
+++ b/cayenne-rop-server/pom.xml
@@ -28,6 +28,8 @@
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
         </dependency>
         <dependency>
             <groupId>org.apache.cayenne</groupId>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContext.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContext.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContext.java
new file mode 100644
index 0000000..c352ae0
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContext.java
@@ -0,0 +1,391 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.event.EventManager;
+import org.apache.cayenne.graph.CompoundDiff;
+import org.apache.cayenne.graph.GraphDiff;
+import org.apache.cayenne.graph.GraphManager;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.util.EventUtil;
+import org.apache.cayenne.validation.ValidationException;
+import org.apache.cayenne.validation.ValidationResult;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A default generic implementation of ObjectContext suitable for accessing
+ * Cayenne from either an ORM or a client tiers. Communicates with Cayenne via a
+ * {@link org.apache.cayenne.DataChannel}.
+ * 
+ * @since 1.2
+ */
+public class CayenneContext extends BaseContext {
+
+    CayenneContextGraphManager graphManager;
+
+    // object that merges "backdoor" changes that come from the channel.
+    CayenneContextMergeHandler mergeHandler;
+
+    /**
+     * Creates a new CayenneContext with no channel and disabled graph events.
+     */
+    public CayenneContext() {
+        this(null);
+    }
+
+    /**
+     * Creates a new CayenneContext, initializing it with a channel instance.
+     * CayenneContext created using this constructor WILL NOT broadcast graph
+     * change events.
+     */
+    public CayenneContext(DataChannel channel) {
+        this(channel, false, false);
+    }
+
+    /**
+     * Creates a new CayenneContext, initializing it with a channel.
+     */
+    public CayenneContext(DataChannel channel, boolean changeEventsEnabled, boolean lifecyleEventsEnabled) {
+
+        graphManager = new CayenneContextGraphManager(this, changeEventsEnabled, lifecyleEventsEnabled);
+
+        if (channel != null) {
+            attachToChannel(channel);
+        }
+    }
+
+    /**
+     * @since 3.1
+     */
+    @Override
+    protected void attachToChannel(DataChannel channel) {
+        super.attachToChannel(channel);
+
+        if (mergeHandler != null) {
+            mergeHandler.active = false;
+            mergeHandler = null;
+        }
+
+        EventManager eventManager = channel.getEventManager();
+        if (eventManager != null) {
+            mergeHandler = new CayenneContextMergeHandler(this);
+
+            // listen to our channel events...
+            // note that we must reset listener on channel switch, as there is
+            // no
+            // guarantee that a new channel uses the same EventManager.
+            EventUtil.listenForChannelEvents(channel, mergeHandler);
+        }
+    }
+
+    /**
+     * Returns true if this context posts individual object modification events.
+     * Subject used for these events is
+     * <code>ObjectContext.GRAPH_CHANGED_SUBJECT</code>.
+     */
+    public boolean isChangeEventsEnabled() {
+        return graphManager.changeEventsEnabled;
+    }
+
+    /**
+     * Returns true if this context posts lifecycle events. Subjects used for
+     * these events are
+     * <code>ObjectContext.GRAPH_COMMIT_STARTED_SUBJECT, ObjectContext.GRAPH_COMMITTED_SUBJECT,
+     * ObjectContext.GRAPH_COMMIT_ABORTED_SUBJECT, ObjectContext.GRAPH_ROLLEDBACK_SUBJECT.</code>
+     * .
+     */
+    public boolean isLifecycleEventsEnabled() {
+        return graphManager.lifecycleEventsEnabled;
+    }
+
+    @Override
+    public GraphManager getGraphManager() {
+        return graphManager;
+    }
+
+    CayenneContextGraphManager internalGraphManager() {
+        return graphManager;
+    }
+
+    /**
+     * Commits changes to uncommitted objects. First checks if there are changes
+     * in this context and if any changes are detected, sends a commit message
+     * to remote Cayenne service via an internal instance of CayenneConnector.
+     */
+    @Override
+    public void commitChanges() {
+        doCommitChanges(true);
+    }
+
+    GraphDiff doCommitChanges(boolean cascade) {
+
+        int syncType = cascade ? DataChannel.FLUSH_CASCADE_SYNC : DataChannel.FLUSH_NOCASCADE_SYNC;
+
+        GraphDiff commitDiff = null;
+
+        synchronized (graphManager) {
+
+            if (graphManager.hasChanges()) {
+
+                if (isValidatingObjectsOnCommit()) {
+                    ValidationResult result = new ValidationResult();
+                    Iterator<?> it = graphManager.dirtyNodes().iterator();
+                    while (it.hasNext()) {
+                        Persistent p = (Persistent) it.next();
+                        if (p instanceof Validating) {
+                            switch (p.getPersistenceState()) {
+                            case PersistenceState.NEW:
+                                ((Validating) p).validateForInsert(result);
+                                break;
+                            case PersistenceState.MODIFIED:
+                                ((Validating) p).validateForUpdate(result);
+                                break;
+                            case PersistenceState.DELETED:
+                                ((Validating) p).validateForDelete(result);
+                                break;
+                            }
+                        }
+                    }
+
+                    if (result.hasFailures()) {
+                        throw new ValidationException(result);
+                    }
+                }
+
+                graphManager.graphCommitStarted();
+
+                GraphDiff changes = graphManager.getDiffsSinceLastFlush();
+
+                try {
+                    commitDiff = channel.onSync(this, changes, syncType);
+                } catch (Throwable th) {
+                    graphManager.graphCommitAborted();
+
+                    if (th instanceof CayenneRuntimeException) {
+                        throw (CayenneRuntimeException) th;
+                    } else {
+                        throw new CayenneRuntimeException("Commit error", th);
+                    }
+                }
+
+                graphManager.graphCommitted(commitDiff);
+
+                // this event is caught by peer nested ObjectContexts to
+                // synchronize the
+                // state
+                fireDataChannelCommitted(this, changes);
+            }
+        }
+
+        return commitDiff;
+    }
+
+    @Override
+    public void commitChangesToParent() {
+        doCommitChanges(false);
+    }
+
+    @Override
+    public void rollbackChanges() {
+        synchronized (graphManager) {
+            if (graphManager.hasChanges()) {
+
+                GraphDiff diff = graphManager.getDiffs();
+                graphManager.graphReverted();
+
+                channel.onSync(this, diff, DataChannel.ROLLBACK_CASCADE_SYNC);
+                fireDataChannelRolledback(this, diff);
+            }
+        }
+    }
+
+    @Override
+    public void rollbackChangesLocally() {
+        synchronized (graphManager) {
+            if (graphManager.hasChanges()) {
+                GraphDiff diff = graphManager.getDiffs();
+                graphManager.graphReverted();
+
+                fireDataChannelRolledback(this, diff);
+            }
+        }
+    }
+
+    /**
+     * Creates and registers a new Persistent object instance.
+     */
+    @Override
+    public <T> T newObject(Class<T> persistentClass) {
+        if (persistentClass == null) {
+            throw new NullPointerException("Persistent class can't be null.");
+        }
+
+        ObjEntity entity = getEntityResolver().getObjEntity(persistentClass);
+        if (entity == null) {
+            throw new CayenneRuntimeException("No entity mapped for class: %s", persistentClass);
+        }
+
+        ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName());
+        @SuppressWarnings("unchecked")
+        T object = (T) descriptor.createObject();
+        descriptor.injectValueHolders(object);
+        registerNewObject((Persistent) object, entity.getName(), descriptor);
+        return object;
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public void registerNewObject(Object object) {
+        if (object == null) {
+            throw new NullPointerException("An attempt to register null object.");
+        }
+
+        ObjEntity entity = getEntityResolver().getObjEntity(object.getClass());
+        ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName());
+        registerNewObject((Persistent) object, entity.getName(), descriptor);
+    }
+
+    /**
+     * Runs a query, returning result as list.
+     */
+    @Override
+    public List performQuery(Query query) {
+        List result = onQuery(this, query).firstList();
+        return result != null ? result : new ArrayList<>(1);
+    }
+
+    @Override
+    public QueryResponse performGenericQuery(Query query) {
+        return onQuery(this, query);
+    }
+
+    public QueryResponse onQuery(ObjectContext context, Query query) {
+        return new CayenneContextQueryAction(this, context, query).execute();
+    }
+
+    @Override
+    public Collection<?> uncommittedObjects() {
+        synchronized (graphManager) {
+            return graphManager.dirtyNodes();
+        }
+    }
+
+    @Override
+    public Collection<?> deletedObjects() {
+        synchronized (graphManager) {
+            return graphManager.dirtyNodes(PersistenceState.DELETED);
+        }
+    }
+
+    @Override
+    public Collection<?> modifiedObjects() {
+        synchronized (graphManager) {
+            return graphManager.dirtyNodes(PersistenceState.MODIFIED);
+        }
+    }
+
+    @Override
+    public Collection<?> newObjects() {
+        synchronized (graphManager) {
+            return graphManager.dirtyNodes(PersistenceState.NEW);
+        }
+    }
+
+    // ****** non-public methods ******
+
+    void registerNewObject(Persistent object, String entityName, ClassDescriptor descriptor) {
+        /**
+         * We should create new id only if it is not set for this object. It
+         * could have been created, for instance, in child context
+         */
+        ObjectId id = object.getObjectId();
+        if (id == null) {
+            id = new ObjectId(entityName);
+            object.setObjectId(id);
+        }
+
+        injectInitialValue(object);
+    }
+
+    Persistent createFault(ObjectId id) {
+        ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName());
+
+        Persistent object = (Persistent) descriptor.createObject();
+
+        object.setPersistenceState(PersistenceState.HOLLOW);
+        object.setObjectContext(this);
+        object.setObjectId(id);
+
+        graphManager.registerNode(id, object);
+
+        return object;
+    }
+
+    @Override
+    protected GraphDiff onContextFlush(ObjectContext originatingContext, GraphDiff changes, boolean cascade) {
+
+        boolean childContext = this != originatingContext && changes != null;
+
+        if (childContext) {
+
+            // PropertyChangeProcessingStrategy oldStrategy =
+            // getPropertyChangeProcessingStrategy();
+            // setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy.RECORD);
+            try {
+                changes.apply(new CayenneContextChildDiffLoader(this));
+            } finally {
+                // setPropertyChangeProcessingStrategy(oldStrategy);
+            }
+
+            fireDataChannelChanged(originatingContext, changes);
+        }
+
+        return (cascade) ? doCommitChanges(true) : new CompoundDiff();
+    }
+
+    /**
+     * Returns <code>true</code> if there are any modified, deleted or new
+     * objects registered with this CayenneContext, <code>false</code>
+     * otherwise.
+     */
+    public boolean hasChanges() {
+        return graphManager.hasChanges();
+    }
+
+    /**
+     * This method simply returns an iterator over a list of selected objects.
+     * There's no performance benefit of using it vs. regular "select".
+     * 
+     * @since 4.0
+     */
+    public <T> ResultIterator<T> iterator(org.apache.cayenne.query.Select<T> query) {
+        List<T> objects = select(query);
+        return new CollectionResultIterator<T>(objects);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
new file mode 100644
index 0000000..151b72e
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
@@ -0,0 +1,142 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.graph.ChildDiffLoader;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.AttributeProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyDescriptor;
+import org.apache.cayenne.reflect.PropertyVisitor;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.reflect.ToOneProperty;
+
+/**
+ * Used for loading child's CayenneContext changes to parent context.
+ * 
+ * @since 3.0
+ */
+class CayenneContextChildDiffLoader extends ChildDiffLoader {
+
+    public CayenneContextChildDiffLoader(CayenneContext context) {
+        super(context);
+    }
+
+    @Override
+    public void nodePropertyChanged(
+            Object nodeId,
+            String property,
+            Object oldValue,
+            Object newValue) {
+
+        super.nodePropertyChanged(nodeId, property, oldValue, newValue);
+
+        Persistent object = (Persistent) context.getGraphManager().getNode(nodeId);
+        context.propertyChanged(object, property, oldValue, newValue);
+    }
+
+    @Override
+    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+
+        final Persistent source = findObject(nodeId);
+        final Persistent target = findObject(targetNodeId);
+
+        // if a target was later deleted, the diff for arcCreated is still preserved and
+        // can result in NULL target here.
+        if (target == null) {
+            return;
+        }
+
+        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
+                ((ObjectId) nodeId).getEntityName());
+        ArcProperty property = (ArcProperty) descriptor.getProperty(arcId.toString());
+
+        property.visit(new PropertyVisitor() {
+
+            public boolean visitAttribute(AttributeProperty property) {
+                return false;
+            }
+
+            public boolean visitToMany(ToManyProperty property) {
+                property.addTargetDirectly(source, target);
+                return false;
+            }
+
+            public boolean visitToOne(ToOneProperty property) {
+                property.setTarget(source, target, false);
+                return false;
+            }
+        });
+        context.propertyChanged(source, (String) arcId, null, target);
+    }
+
+    @Override
+    public void arcDeleted(Object nodeId, final Object targetNodeId, Object arcId) {
+        final Persistent source = findObject(nodeId);
+
+        // needed as sometime temporary objects are evoked from the context before
+        // changing their relationships
+        if (source == null) {
+            return;
+        }
+
+        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
+                ((ObjectId) nodeId).getEntityName());
+        PropertyDescriptor property = descriptor.getProperty(arcId.toString());
+
+        final Persistent[] target = new Persistent[1];
+        target[0] = findObject(targetNodeId);
+        
+        property.visit(new PropertyVisitor() {
+
+            public boolean visitAttribute(AttributeProperty property) {
+                return false;
+            }
+
+            public boolean visitToMany(ToManyProperty property) {
+                if (target[0] == null) {
+
+                    // this is usually the case when a NEW object was deleted and then
+                    // its relationships were manipulated; so try to locate the object
+                    // in the collection ... the performance of this is rather dubious
+                    // of course...
+                    target[0] = findObjectInCollection(targetNodeId, property
+                            .readProperty(source));
+                }
+
+                if (target[0] == null) {
+                    // ignore?
+                }
+                else {
+                    property.removeTargetDirectly(source, target[0]);
+                }
+
+                return false;
+            }
+
+            public boolean visitToOne(ToOneProperty property) {
+                property.setTarget(source, null, false);
+                return false;
+            }
+        });
+
+        context.propertyChanged(source, (String) arcId, target[0], null);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
new file mode 100644
index 0000000..f4865c0
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
@@ -0,0 +1,379 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.event.EventManager;
+import org.apache.cayenne.event.EventSubject;
+import org.apache.cayenne.graph.ArcCreateOperation;
+import org.apache.cayenne.graph.ArcDeleteOperation;
+import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.graph.GraphDiff;
+import org.apache.cayenne.graph.GraphEvent;
+import org.apache.cayenne.graph.GraphMap;
+import org.apache.cayenne.graph.NodeCreateOperation;
+import org.apache.cayenne.graph.NodeDeleteOperation;
+import org.apache.cayenne.graph.NodeIdChangeOperation;
+import org.apache.cayenne.graph.NodePropertyChangeOperation;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyException;
+import org.apache.cayenne.reflect.ToManyMapProperty;
+import org.apache.cayenne.util.PersistentObjectMap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A GraphMap extension that works together with {@link ObjectContext} to track persistent object
+ * changes and send events.
+ * 
+ * @since 1.2
+ */
+final class CayenneContextGraphManager extends GraphMap {
+
+    static final String COMMIT_MARKER = "commit";
+    static final String FLUSH_MARKER = "flush";
+
+    CayenneContext context;
+    Collection<Object> deadIds;
+    boolean changeEventsEnabled;
+    boolean lifecycleEventsEnabled;
+
+    ObjectContextStateLog stateLog;
+    ObjectContextChangeLog changeLog;
+
+    CayenneContextGraphManager(CayenneContext context, boolean changeEventsEnabled,
+            boolean lifecycleEventsEnabled) {
+
+        this.context = context;
+        this.changeEventsEnabled = changeEventsEnabled;
+        this.lifecycleEventsEnabled = lifecycleEventsEnabled;
+
+        this.stateLog = new ObjectContextStateLog(this);
+        this.changeLog = new ObjectContextChangeLog();
+    }
+
+    boolean hasChanges() {
+        return changeLog.size() > 0;
+    }
+
+    boolean hasChangesSinceLastFlush() {
+        int size = changeLog.hasMarker(FLUSH_MARKER) ? changeLog
+                .sizeAfterMarker(FLUSH_MARKER) : changeLog.size();
+        return size > 0;
+    }
+
+    GraphDiff getDiffs() {
+        return changeLog.getDiffs();
+    }
+
+    GraphDiff getDiffsSinceLastFlush() {
+        return changeLog.hasMarker(FLUSH_MARKER) ? changeLog
+                .getDiffsAfterMarker(FLUSH_MARKER) : changeLog.getDiffs();
+    }
+
+    Collection<Object> dirtyNodes() {
+        return stateLog.dirtyNodes();
+    }
+
+    Collection<Object> dirtyNodes(int state) {
+        return stateLog.dirtyNodes(state);
+    }
+
+    @Override
+    public synchronized Object unregisterNode(Object nodeId) {
+        Object node = super.unregisterNode(nodeId);
+
+        // remove node from other collections...
+        if (node != null) {
+            stateLog.unregisterNode(nodeId);
+            changeLog.unregisterNode(nodeId);
+            Persistent object = (Persistent)node;
+            object.setObjectContext(null);
+            object.setPersistenceState(PersistenceState.TRANSIENT);
+            return node;
+        }
+
+        return null;
+    }
+
+    // ****** Sync Events API *****
+    /**
+     * Clears commit marker, but keeps all recorded operations.
+     */
+    void graphCommitAborted() {
+        changeLog.removeMarker(COMMIT_MARKER);
+    }
+
+    /**
+     * Sets commit start marker in the change log. If events are enabled, posts commit
+     * start event.
+     */
+    void graphCommitStarted() {
+        changeLog.setMarker(COMMIT_MARKER);
+    }
+
+    void graphCommitted(GraphDiff parentSyncDiff) {
+        if (parentSyncDiff != null) {
+            new CayenneContextMergeHandler(context).merge(parentSyncDiff);
+        }
+
+        remapTargets();
+
+        stateLog.graphCommitted();
+        reset();
+
+        if (lifecycleEventsEnabled) {
+            // include all diffs after the commit start marker.
+            // We fire event as if it was posted by parent channel, so that
+            // nested contexts could catch it
+            context.fireDataChannelCommitted(context.getChannel(), parentSyncDiff);
+        }
+    }
+
+    /**
+     * Remaps keys in to-many map relationships that contain dirty objects with
+     * potentially modified properties.
+     */
+    private void remapTargets() {
+
+        Iterator<Object> it = stateLog.dirtyIds().iterator();
+
+        EntityResolver resolver = context.getEntityResolver();
+
+        while (it.hasNext()) {
+            ObjectId id = (ObjectId) it.next();
+            ClassDescriptor descriptor = resolver.getClassDescriptor(id.getEntityName());
+
+            Collection<ArcProperty> mapArcProperties = descriptor.getMapArcProperties();
+            if (!mapArcProperties.isEmpty()) {
+
+                Object object = getNode(id);
+
+                for (ArcProperty arc : mapArcProperties) {
+                    ToManyMapProperty reverseArc = (ToManyMapProperty) arc
+                            .getComplimentaryReverseArc();
+
+                    Object source = arc.readPropertyDirectly(object);
+                    if (source != null && !reverseArc.isFault(source)) {
+                        remapTarget(reverseArc, source, object);
+                    }
+                }
+            }
+        }
+    }
+
+    // clone of DataDomainSyncBucket.remapTarget
+    private final void remapTarget(
+            ToManyMapProperty property,
+            Object source,
+            Object target) throws PropertyException {
+
+        @SuppressWarnings("unchecked")
+        Map<Object, Object> map = (Map<Object, Object>) property.readProperty(source);
+
+        Object newKey = property.getMapKey(target);
+        Object currentValue = map.get(newKey);
+
+        if (currentValue == target) {
+            // nothing to do
+            return;
+        }
+        // else - do not check for conflicts here (i.e. another object mapped for the same
+        // key), as we have no control of the order in which this method is called, so
+        // another object may be remapped later by the caller
+
+        // must do a slow map scan to ensure the object is not mapped under a different
+        // key...
+        Iterator<?> it = map.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<?, ?> e = (Map.Entry<?, ?>) it.next();
+            if (e.getValue() == target) {
+                // this remove does not trigger event in PersistentObjectMap
+                it.remove();
+                break;
+            }
+        }
+
+        // TODO: (andrey, 25/11/09 - this is a hack to prevent event triggering
+        // (and concurrent exceptions)
+        // should find a way to get rid of type casting
+        ((PersistentObjectMap) map).putDirectly(newKey, target);
+    }
+
+    void graphFlushed() {
+        changeLog.setMarker(FLUSH_MARKER);
+    }
+
+    void graphReverted() {
+        GraphDiff diff = changeLog.getDiffs();
+
+        diff.undo(new RollbackChangeHandler());
+        stateLog.graphReverted();
+        reset();
+
+        if (lifecycleEventsEnabled) {
+            context.fireDataChannelRolledback(context, diff);
+        }
+    }
+
+    // ****** GraphChangeHandler API ******
+    // =====================================================
+
+    @Override
+    public synchronized void nodeIdChanged(Object nodeId, Object newId) {
+        stateLog.nodeIdChanged(nodeId, newId);
+        processChange(new NodeIdChangeOperation(nodeId, newId));
+    }
+
+    @Override
+    public synchronized void nodeCreated(Object nodeId) {
+        stateLog.nodeCreated(nodeId);
+        processChange(new NodeCreateOperation(nodeId));
+    }
+
+    @Override
+    public synchronized void nodeRemoved(Object nodeId) {
+        stateLog.nodeRemoved(nodeId);
+        processChange(new NodeDeleteOperation(nodeId));
+    }
+
+    @Override
+    public synchronized void nodePropertyChanged(
+            Object nodeId,
+            String property,
+            Object oldValue,
+            Object newValue) {
+
+        stateLog.nodePropertyChanged(nodeId, property, oldValue, newValue);
+        processChange(new NodePropertyChangeOperation(
+                nodeId,
+                property,
+                oldValue,
+                newValue));
+    }
+
+    @Override
+    public synchronized void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+        stateLog.arcCreated(nodeId, targetNodeId, arcId);
+        processChange(new ArcCreateOperation(nodeId, targetNodeId, arcId));
+    }
+
+    @Override
+    public synchronized void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+        stateLog.arcDeleted(nodeId, targetNodeId, arcId);
+        processChange(new ArcDeleteOperation(nodeId, targetNodeId, arcId));
+    }
+
+    // ****** helper methods ******
+    // =====================================================
+
+    private void processChange(GraphDiff diff) {
+        changeLog.addOperation(diff);
+
+        if (changeEventsEnabled) {
+            context.fireDataChannelChanged(context, diff);
+        }
+    }
+
+    /**
+     * Wraps GraphDiff in a GraphEvent and sends it via EventManager with specified
+     * subject.
+     */
+    void send(GraphDiff diff, EventSubject subject, Object eventSource) {
+        EventManager manager = (context.getChannel() != null) ? context
+                .getChannel()
+                .getEventManager() : null;
+
+        if (manager != null) {
+            GraphEvent e = new GraphEvent(context, eventSource, diff);
+            manager.postEvent(e, subject);
+        }
+    }
+
+    void reset() {
+        changeLog.reset();
+
+        if (deadIds != null) {
+            // unregister dead ids...
+            for (final Object deadId : deadIds) {
+                nodes.remove(deadId);
+            }
+
+            deadIds = null;
+        }
+    }
+
+    Collection<Object> deadIds() {
+        if (deadIds == null) {
+            deadIds = new ArrayList<>();
+        }
+
+        return deadIds;
+    }
+
+    /**
+     * This change handler is used to perform rollback actions for Cayenne context
+     */
+    class RollbackChangeHandler implements GraphChangeHandler {
+
+        public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+            context.mergeHandler.arcCreated(nodeId, targetNodeId, arcId);
+            CayenneContextGraphManager.this.arcCreated(nodeId, targetNodeId, arcId);
+        }
+
+        public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+            context.mergeHandler.arcDeleted(nodeId, targetNodeId, arcId);
+            CayenneContextGraphManager.this.arcDeleted(nodeId, targetNodeId, arcId);
+        }
+
+        public void nodeCreated(Object nodeId) {
+            CayenneContextGraphManager.this.nodeCreated(nodeId);
+        }
+
+        public void nodeIdChanged(Object nodeId, Object newId) {
+            CayenneContextGraphManager.this.nodeIdChanged(nodeId, newId);
+        }
+
+        /**
+         * Need to write property directly to this context
+         */
+        public void nodePropertyChanged(
+                Object nodeId,
+                String property,
+                Object oldValue,
+                Object newValue) {
+            context.mergeHandler
+                    .nodePropertyChanged(nodeId, property, oldValue, newValue);
+            CayenneContextGraphManager.this.nodePropertyChanged(
+                    nodeId,
+                    property,
+                    oldValue,
+                    newValue);
+        }
+
+        public void nodeRemoved(Object nodeId) {
+            CayenneContextGraphManager.this.nodeRemoved(nodeId);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
new file mode 100644
index 0000000..f490128
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
@@ -0,0 +1,274 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.graph.GraphDiff;
+import org.apache.cayenne.graph.GraphEvent;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyDescriptor;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.util.Util;
+
+/**
+ * An object that merges "backdoor" modifications of the object graph coming from the
+ * underlying DataChannel. When doing an update, CayenneContextMergeHandler blocks
+ * broadcasting of GraphManager events.
+ * 
+ * @since 1.2
+ */
+class CayenneContextMergeHandler implements GraphChangeHandler, DataChannelListener {
+
+    CayenneContext context;
+    boolean active;
+
+    CayenneContextMergeHandler(CayenneContext context) {
+        this.context = context;
+        this.active = true;
+    }
+
+    // ******* DataChannelListener methods *******
+
+    public void graphChanged(final GraphEvent e) {
+        // process flush
+        if (shouldProcessEvent(e) && e.getDiff() != null) {
+            runWithEventsDisabled(new Runnable() {
+
+                public void run() {
+                    e.getDiff().apply(CayenneContextMergeHandler.this);
+
+                }
+            });
+
+            // post event outside of "execute" to make sure it is sent
+            repostAfterMerge(e);
+        }
+    }
+
+    public void graphFlushed(final GraphEvent e) {
+        // TODO (Andrus, 10/17/2005) - there are a few problems with commit processing:
+
+        // 1. Event mechanism reliability:
+        // - events may come out of order (commit and then preceeding flush)
+        // - events may be missing all together (commit arrived, while prior flush did
+        // not)
+        // Possible solution - an "event_version_id" to be used for optimistic locking
+
+        // 2. We don't know if our own dirty objects were committed or not...
+        // For now we will simply merge the changes, and keep the context dirty
+
+        if (shouldProcessEvent(e)) {
+
+            runWithEventsDisabled(new Runnable() {
+
+                public void run() {
+
+                    if (e.getDiff() != null) {
+                        e.getDiff().apply(CayenneContextMergeHandler.this);
+                    }
+                }
+            });
+
+            // post event outside of "execute" to make sure it is sent
+            repostAfterMerge(e);
+        }
+    }
+
+    public void graphRolledback(final GraphEvent e) {
+
+        // TODO: andrus, 3/29/2007: per CAY-771, if a LOCAL peer context posted the event,
+        // just ignore it, however if the REMOTE peer reverted the parent remote
+        // DataContext, we need to invalidate stale committed objects...
+    }
+
+    // ******* End DataChannelListener methods *******
+
+    void repostAfterMerge(GraphEvent originalEvent) {
+        // though the subject is CHANGE, "merge" events are really lifecycle.
+        if (context.isLifecycleEventsEnabled()) {
+            context.fireDataChannelChanged(originalEvent.getSource(), originalEvent.getDiff());
+        }
+    }
+
+    /**
+     * Executes merging of the external diff.
+     */
+    void merge(final GraphDiff diff) {
+        runWithEventsDisabled(new Runnable() {
+
+            public void run() {
+                diff.apply(CayenneContextMergeHandler.this);
+            }
+        });
+    }
+
+    // ******* GraphChangeHandler methods *********
+
+    public void nodeIdChanged(Object nodeId, Object newId) {
+        // do not unregister the node just yet... only put replaced id in deadIds to
+        // remove it later. Otherwise stored operations will not work
+        Object node = context.internalGraphManager().getNode(nodeId);
+
+        if (node != null) {
+            context.internalGraphManager().deadIds().add(nodeId);
+            context.internalGraphManager().registerNode(newId, node);
+
+            if (node instanceof Persistent) {
+                // inject new id
+                ((Persistent) node).setObjectId((ObjectId) newId);
+            }
+        }
+    }
+
+    public void nodeCreated(Object nodeId) {
+        // ignore
+    }
+
+    public void nodeRemoved(Object nodeId) {
+        context.getGraphManager().unregisterNode(nodeId);
+    }
+
+    public void nodePropertyChanged(
+            Object nodeId,
+            String property,
+            Object oldValue,
+            Object newValue) {
+
+        Object object = context.internalGraphManager().getNode(nodeId);
+        if (object != null) {
+
+            // do not override local changes....
+            PropertyDescriptor p = propertyForId(nodeId, property);
+            if (Util.nullSafeEquals(p.readPropertyDirectly(object), oldValue)) {
+
+                p.writePropertyDirectly(object, oldValue, newValue);
+            }
+        }
+    }
+
+    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+        // null source or target likely means the object is not faulted yet... Faults
+        // shouldn't get disturbed by adding/removing arcs
+
+        Object source = context.internalGraphManager().getNode(nodeId);
+        if (source == null) {
+            // no need to connect non-existent object
+            return;
+        }
+
+        // TODO (Andrus, 10/17/2005) - check for local modifications to avoid
+        // overwriting...
+
+        ArcProperty p = (ArcProperty) propertyForId(nodeId, arcId.toString());
+        if (p.isFault(source)) {
+            return;
+        }
+
+        Object target = context.internalGraphManager().getNode(targetNodeId);
+        if (target == null) {
+            target = context.createFault((ObjectId) targetNodeId);
+        }
+
+        try {
+            if (p instanceof ToManyProperty) {
+                ((ToManyProperty) p).addTargetDirectly(source, target);
+            }
+            else {
+                p.writePropertyDirectly(source, null, target);
+            }
+        }
+        finally {
+        }
+    }
+
+    public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+
+        // null source or target likely means the object is not faulted yet... Faults
+        // shouldn't get disturbed by adding/removing arcs
+
+        Object source = context.internalGraphManager().getNode(nodeId);
+        if (source == null) {
+            // no need to disconnect non-existent object
+            return;
+        }
+
+        // (see "TODO" in 'arcCreated')
+        ArcProperty p = (ArcProperty) propertyForId(nodeId, arcId.toString());
+        if (p.isFault(source)) {
+            return;
+        }
+
+        Object target = context.internalGraphManager().getNode(targetNodeId);
+        if (target == null) {
+            target = context.createFault((ObjectId) targetNodeId);
+        }
+
+        try {
+            if (p instanceof ToManyProperty) {
+                ((ToManyProperty) p).removeTargetDirectly(source, target);
+            }
+            else {
+                p.writePropertyDirectly(source, target, null);
+            }
+        }
+        finally {
+        }
+    }
+
+    private PropertyDescriptor propertyForId(Object nodeId, String propertyName) {
+        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
+                ((ObjectId) nodeId).getEntityName());
+        return descriptor.getProperty(propertyName);
+    }
+
+    // Returns true if this object is active; an event came from our channel, but did not
+    // originate in it.
+    boolean shouldProcessEvent(GraphEvent e) {
+        // only process events that came from our channel, but did not originate in it
+        // (i.e. likely posted by EventBridge)
+        return active
+                && e.getSource() == context.getChannel()
+                && e.getPostedBy() != context
+                && e.getPostedBy() != context.getChannel();
+    }
+
+    // executes a closure, disabling ObjectContext events for the duration of the
+    // execution.
+
+    private void runWithEventsDisabled(Runnable closure) {
+
+        synchronized (context.internalGraphManager()) {
+            boolean changeEventsEnabled = context.internalGraphManager().changeEventsEnabled;
+            context.internalGraphManager().changeEventsEnabled = false;
+
+            boolean lifecycleEventsEnabled = context.internalGraphManager().lifecycleEventsEnabled;
+            context.internalGraphManager().lifecycleEventsEnabled = false;
+
+            try {
+                closure.run();
+            }
+            finally {
+                context.internalGraphManager().changeEventsEnabled = changeEventsEnabled;
+                context.internalGraphManager().lifecycleEventsEnabled = lifecycleEventsEnabled;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java
new file mode 100644
index 0000000..e0b693c
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/CayenneContextQueryAction.java
@@ -0,0 +1,186 @@
+/*****************************************************************
+ *   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;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cayenne.cache.QueryCacheEntryFactory;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.RefreshQuery;
+import org.apache.cayenne.reflect.AttributeProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyVisitor;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.reflect.ToOneProperty;
+import org.apache.cayenne.remote.RemoteIncrementalFaultList;
+import org.apache.cayenne.util.ListResponse;
+import org.apache.cayenne.util.ObjectContextQueryAction;
+
+/**
+ * @since 1.2
+ */
+class CayenneContextQueryAction extends ObjectContextQueryAction {
+
+    CayenneContextQueryAction(CayenneContext actingContext, ObjectContext targetContext,
+            Query query) {
+        super(actingContext, targetContext, query);
+    }
+
+    @Override
+    protected boolean interceptPaginatedQuery() {
+        if (metadata.getPageSize() > 0) {
+            response = new ListResponse(new RemoteIncrementalFaultList(
+                    actingContext,
+                    query));
+            return DONE;
+        }
+
+        return !DONE;
+    }
+
+    @Override
+    protected QueryCacheEntryFactory getCacheObjectFactory() {
+        return new QueryCacheEntryFactory() {
+
+            public List createObject() {
+                if (interceptPaginatedQuery() != DONE) {
+                    runQuery();
+                }
+                return response.firstList();
+            }
+        };
+    }
+
+    @Override
+    protected boolean interceptRefreshQuery() {
+        if (query instanceof RefreshQuery) {
+            RefreshQuery refreshQuery = (RefreshQuery) query;
+
+            CayenneContext context = (CayenneContext) actingContext;
+
+            // handle 4 separate scenarios, but do not combine them as it will be
+            // unclear how to handle cascading behavior
+
+            // 1. refresh all
+            if (refreshQuery.isRefreshAll()) {
+
+                invalidateLocally(context.internalGraphManager(), context
+                        .internalGraphManager()
+                        .registeredNodes()
+                        .iterator());
+                context.getQueryCache().clear();
+
+                // cascade
+                return !DONE;
+            }
+
+            // 2. invalidate object collection
+            Collection<?> objects = refreshQuery.getObjects();
+            if (objects != null && !objects.isEmpty()) {
+
+                invalidateLocally(context.internalGraphManager(), objects.iterator());
+
+                // cascade
+                return !DONE;
+            }
+
+            // 3. refresh query - have to do it eagerly to refresh the objects involved
+            if (refreshQuery.getQuery() != null) {
+                Query cachedQuery = refreshQuery.getQuery();
+
+                String cacheKey = cachedQuery
+                        .getMetaData(context.getEntityResolver())
+                        .getCacheKey();
+                context.getQueryCache().remove(cacheKey);
+
+                this.response = context.performGenericQuery(cachedQuery);
+
+                // do not cascade to avoid running query twice
+                return DONE;
+            }
+
+            // 4. refresh groups...
+            if (refreshQuery.getGroupKeys() != null
+                    && refreshQuery.getGroupKeys().length > 0) {
+
+                String[] groups = refreshQuery.getGroupKeys();
+                for (String group : groups) {
+                    context.getQueryCache().removeGroup(group);
+                }
+
+                // cascade group invalidation
+                return !DONE;
+            }
+        }
+
+        return !DONE;
+    }
+
+    private void invalidateLocally(CayenneContextGraphManager graphManager, Iterator<?> it) {
+        if (!it.hasNext()) {
+            return;
+        }
+
+        EntityResolver resolver = actingContext.getEntityResolver();
+
+        while (it.hasNext()) {
+            final Persistent object = (Persistent) it.next();
+
+            // we don't care about NEW objects,
+            // but we still do care about HOLLOW, since snapshot might still be
+            // present
+            if (object.getPersistenceState() == PersistenceState.NEW) {
+                continue;
+            }
+
+            ObjectId id = object.getObjectId();
+
+            // per CAY-1082 ROP objects (unlike CayenneDataObject) require all
+            // relationship faults invalidation.
+            ClassDescriptor descriptor = resolver.getClassDescriptor(id.getEntityName());
+            PropertyVisitor arcInvalidator = new PropertyVisitor() {
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    return true;
+                }
+
+                public boolean visitToMany(ToManyProperty property) {
+                    property.invalidate(object);
+                    return true;
+                }
+
+                public boolean visitToOne(ToOneProperty property) {
+                    property.invalidate(object);
+                    return true;
+                }
+            };
+
+            descriptor.visitProperties(arcInvalidator);
+            object.setPersistenceState(PersistenceState.HOLLOW);
+            
+            // remove cached changes
+            graphManager.changeLog.unregisterNode(id);
+            graphManager.stateLog.unregisterNode(id);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.java
new file mode 100644
index 0000000..26f2f1b
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/BootstrapMessage.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.remote;
+
+/**
+ * A message sent to a remote service to request Cayenne mapping info.
+ * 
+ * @since 1.2
+ */
+public class BootstrapMessage implements ClientMessage {
+
+    /**
+     * Returns a description of the type of message. In this case always "Bootstrap".
+     */
+    @Override
+    public String toString() {
+        return "Bootstrap";
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java
new file mode 100644
index 0000000..e0740b3
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/ClientMessage.java
@@ -0,0 +1,31 @@
+/*****************************************************************
+ *   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.remote;
+
+import java.io.Serializable;
+
+/**
+ * A tag interface representing a message sent by a remote client to Cayenne service.
+ * 
+ * @since 1.2
+ */
+public interface ClientMessage extends Serializable {
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
new file mode 100644
index 0000000..51c7946
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
@@ -0,0 +1,73 @@
+/*****************************************************************
+ *   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.remote;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryMetadataProxy;
+import org.apache.cayenne.query.QueryRouter;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SQLActionVisitor;
+
+/**
+ * A client wrapper for the incremental query that overrides the metadata to ensure that
+ * query result is cached on the server, so that subranges could be retrieved at a later
+ * time.
+ * 
+ * @since 1.2
+ */
+class IncrementalQuery implements Query {
+
+    private Query query;
+    private String cacheKey;
+
+    IncrementalQuery(Query query, String cacheKey) {
+        this.query = query;
+        this.cacheKey = cacheKey;
+    }
+
+    public QueryMetadata getMetaData(EntityResolver resolver) {
+        final QueryMetadata metadata = query.getMetaData(resolver);
+
+        // the way paginated queries work on the server is that they are never cached
+        // (IncrementalFaultList interception happens before cache interception). So
+        // overriding caching settings in the metadata will only affect
+        // ClientServerChannel behavior
+        return new QueryMetadataProxy(metadata) {
+            public Query getOriginatingQuery() {
+                return null;
+            }
+
+            public String getCacheKey() {
+                return cacheKey;
+            }
+        };
+    }
+
+    public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
+        query.route(router, resolver, substitutedQuery);
+    }
+
+    public SQLAction createSQLAction(SQLActionVisitor visitor) {
+        return query.createSQLAction(visitor);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
new file mode 100644
index 0000000..9b6f563
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
@@ -0,0 +1,300 @@
+/*****************************************************************
+ *   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.remote;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ResultBatchIterator;
+import org.apache.cayenne.ResultIterator;
+import org.apache.cayenne.ResultIteratorCallback;
+import org.apache.cayenne.access.IncrementalFaultList;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryMetadataProxy;
+import org.apache.cayenne.query.QueryRouter;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SQLActionVisitor;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.query.SortOrder;
+import org.apache.cayenne.util.XMLEncoder;
+
+/**
+ * A SelectQuery decorator that overrides the metadata to ensure that query
+ * result is cached on the server, so that subranges could be retrieved at a
+ * later time. Note that a special decorator that is a subclass of SelectQuery
+ * is needed so that {@link IncrementalFaultList} on the server-side could apply
+ * SelectQuery-specific optimizations.
+ * 
+ * @since 3.0
+ */
+class IncrementalSelectQuery<T> extends SelectQuery<T> {
+
+	private SelectQuery<T> query;
+	private String cacheKey;
+
+	IncrementalSelectQuery(SelectQuery<T> delegate, String cacheKey) {
+		this.query = delegate;
+		this.cacheKey = cacheKey;
+	}
+
+	@Override
+	public QueryMetadata getMetaData(EntityResolver resolver) {
+		final QueryMetadata metadata = query.getMetaData(resolver);
+
+		// the way paginated queries work on the server is that they are never
+		// cached
+		// (IncrementalFaultList interception happens before cache
+		// interception). So
+		// overriding caching settings in the metadata will only affect
+		// ClientServerChannel behavior
+		return new QueryMetadataProxy(metadata) {
+			public Query getOriginatingQuery() {
+				return null;
+			}
+
+			public String getCacheKey() {
+				return cacheKey;
+			}
+		};
+	}
+
+	@Override
+	public void addOrdering(Ordering ordering) {
+		query.addOrdering(ordering);
+	}
+
+	@Override
+	public void addOrdering(String sortPathSpec, SortOrder order) {
+		query.addOrdering(sortPathSpec, order);
+	}
+
+	@Override
+	public void addOrderings(Collection<? extends Ordering> orderings) {
+		query.addOrderings(orderings);
+	}
+
+	@Override
+	public PrefetchTreeNode addPrefetch(String prefetchPath) {
+		return query.addPrefetch(prefetchPath);
+	}
+
+	@Override
+	public void andQualifier(Expression e) {
+		query.andQualifier(e);
+	}
+
+	@Override
+	public void clearOrderings() {
+		query.clearOrderings();
+	}
+
+	@Override
+	public void clearPrefetches() {
+		query.clearPrefetches();
+	}
+
+	@Override
+	public SelectQuery<T> createQuery(Map<String, ?> parameters) {
+		return query.createQuery(parameters);
+	}
+
+	@Override
+	public SQLAction createSQLAction(SQLActionVisitor visitor) {
+		return query.createSQLAction(visitor);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		return query.equals(obj);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public String getCacheGroup() {
+		return super.getCacheGroup();
+	}
+
+	@Override
+	public int getFetchLimit() {
+		return query.getFetchLimit();
+	}
+
+	@Override
+	public List<Ordering> getOrderings() {
+		return query.getOrderings();
+	}
+
+	@Override
+	public int getPageSize() {
+		return query.getPageSize();
+	}
+
+	@Override
+	public PrefetchTreeNode getPrefetchTree() {
+		return query.getPrefetchTree();
+	}
+
+	@Override
+	public Expression getQualifier() {
+		return query.getQualifier();
+	}
+
+	@Override
+	public Object getRoot() {
+		return query.getRoot();
+	}
+
+	@Override
+	public int hashCode() {
+		return query.hashCode();
+	}
+
+	@Override
+	public void initWithProperties(Map<String, ?> properties) {
+		query.initWithProperties(properties);
+	}
+
+	@Override
+	public boolean isDistinct() {
+		return query.isDistinct();
+	}
+
+	@Override
+	public boolean isFetchingDataRows() {
+		return query.isFetchingDataRows();
+	}
+
+	@Override
+	public void orQualifier(Expression e) {
+		query.orQualifier(e);
+	}
+
+	@Override
+	public SelectQuery<T> queryWithParameters(Map<String, ?> parameters, boolean pruneMissing) {
+		return query.queryWithParameters(parameters, pruneMissing);
+	}
+
+	@Override
+	public SelectQuery<T> queryWithParameters(Map<String, ?> parameters) {
+		return query.queryWithParameters(parameters);
+	}
+
+	@Override
+	public void removeOrdering(Ordering ordering) {
+		query.removeOrdering(ordering);
+	}
+
+	@Override
+	public void removePrefetch(String prefetchPath) {
+		query.removePrefetch(prefetchPath);
+	}
+
+	@Override
+	public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
+		query.route(router, resolver, substitutedQuery);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public void setCacheGroup(String cacheGroup) {
+		query.setCacheGroup(cacheGroup);
+	}
+
+	@Override
+	public void setDistinct(boolean distinct) {
+		query.setDistinct(distinct);
+	}
+
+	@SuppressWarnings("deprecation")
+	@Override
+	public void setFetchingDataRows(boolean flag) {
+		query.setFetchingDataRows(flag);
+	}
+
+	@Override
+	public void setFetchLimit(int fetchLimit) {
+		query.setFetchLimit(fetchLimit);
+	}
+
+	@Override
+	public void setPageSize(int pageSize) {
+		query.setPageSize(pageSize);
+	}
+
+	@Override
+	public void setPrefetchTree(PrefetchTreeNode prefetchTree) {
+		query.setPrefetchTree(prefetchTree);
+	}
+
+	@Override
+	public void setQualifier(Expression qualifier) {
+		query.setQualifier(qualifier);
+	}
+
+	@Override
+	public void setRoot(Object value) {
+		query.setRoot(value);
+	}
+
+	@Override
+	public String toString() {
+		return query.toString();
+	}
+
+	@Override
+	public List<T> select(ObjectContext context) {
+		return query.select(context);
+	}
+
+	@Override
+	public T selectOne(ObjectContext context) {
+		return query.selectOne(context);
+	}
+
+	@Override
+	public T selectFirst(ObjectContext context) {
+		return query.selectFirst(context);
+	}
+
+	@Override
+	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
+		query.iterate(context, callback);
+	}
+
+	@Override
+	public ResultIterator<T> iterator(ObjectContext context) {
+		return query.iterator(context);
+	}
+
+	@Override
+	public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
+		return query.batchIterator(context, size);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java
new file mode 100644
index 0000000..3c8b4d5
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/QueryMessage.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   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.remote;
+
+import org.apache.cayenne.query.Query;
+
+/**
+ * A message passed to a DataChannel to request a query execution with result returned as
+ * QueryResponse.
+ * 
+ * @since 1.2
+ */
+public class QueryMessage implements ClientMessage {
+
+    protected Query query;
+
+    // for hessian serialization
+    @SuppressWarnings("unused")
+    private QueryMessage() {
+
+    }
+
+    public QueryMessage(Query query) {
+        this.query = query;
+    }
+
+    public Query getQuery() {
+        return query;
+    }
+
+    /**
+     * Returns a description of the type of message. In this case always "Query".
+     */
+    @Override
+    public String toString() {
+        return "Query";
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38f37d79/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
new file mode 100644
index 0000000..f2b5e78
--- /dev/null
+++ b/cayenne-rop-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
@@ -0,0 +1,156 @@
+/*****************************************************************
+ *   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.remote;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryCacheStrategy;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryMetadataProxy;
+import org.apache.cayenne.query.QueryRouter;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SQLActionVisitor;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+/**
+ * A Query that fetches a range of objects from a previously fetched server-side paginated
+ * list. This query is client-only and can't be executed on the server.
+ * 
+ * @since 1.2
+ */
+class RangeQuery implements Query {
+
+    private String cacheKey;
+    private int fetchOffset;
+    private int fetchLimit;
+    private Query originatingQuery;
+
+    // exists for hessian serialization.
+    @SuppressWarnings("unused")
+    private RangeQuery() {
+
+    }
+
+    /**
+     * Creates a query that returns a single page from an existing cached server-side
+     * result list.
+     */
+    RangeQuery(String cacheKey, int fetchStartIndex, int fetchLimit,
+            Query originatingQuery) {
+        this.cacheKey = cacheKey;
+        this.fetchOffset = fetchStartIndex;
+        this.fetchLimit = fetchLimit;
+        this.originatingQuery = originatingQuery;
+    }
+
+    public QueryMetadata getMetaData(EntityResolver resolver) {
+        final QueryMetadata originatingMetadata = originatingQuery.getMetaData(resolver);
+
+        return new QueryMetadataProxy(originatingMetadata) {
+
+            public Query getOriginatingQuery() {
+                return originatingQuery;
+            }
+
+            public List<Object> getResultSetMapping() {
+                return null;
+            }
+
+            public boolean isSingleResultSetMapping() {
+                return false;
+            }
+
+            public String getCacheKey() {
+                return cacheKey;
+            }
+
+            public String getCacheGroup() {
+                return null;
+            }
+
+            public int getFetchOffset() {
+                return fetchOffset;
+            }
+
+            public int getFetchLimit() {
+                return fetchLimit;
+            }
+
+            public int getPageSize() {
+                return 0;
+            }
+
+            /**
+             * @since 3.0
+             */
+            public QueryCacheStrategy getCacheStrategy() {
+                return QueryCacheStrategy.getDefaultStrategy();
+            }
+
+            public DataMap getDataMap() {
+                throw new UnsupportedOperationException();
+            }
+
+            public DbEntity getDbEntity() {
+                throw new UnsupportedOperationException();
+            }
+
+            public ObjEntity getObjEntity() {
+                throw new UnsupportedOperationException();
+            }
+
+            public ClassDescriptor getClassDescriptor() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Procedure getProcedure() {
+                throw new UnsupportedOperationException();
+            }
+
+            public Map<String, String> getPathSplitAliases() {
+                throw new UnsupportedOperationException();
+            }
+
+            public boolean isRefreshingObjects() {
+                throw new UnsupportedOperationException();
+            }
+
+            public int getStatementFetchSize() {
+                return 0;
+            }
+        };
+    }
+
+    public SQLAction createSQLAction(SQLActionVisitor visitor) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
+        throw new UnsupportedOperationException();
+    }
+
+}