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 <stari...@gmail.com> Authored: Tue Dec 12 15:52:43 2017 +0300 Committer: Nikita Timofeev <stari...@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(); + } + +}