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:46 UTC
[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
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();
+ }
+ }
+}