This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch past-M2
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 9acb168b845aa3ac5dd5fe0bbfd42e31e634a8c0
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Jun 7 12:37:27 2026 -0400

    Merging ObjectContextQueryAction to DataContextQueryAction
    
    also moving ObjectContextGraphAction to DataContextGraphAction and mking 
non-public
---
 .../org/apache/cayenne/access/DataContext.java     |   5 +-
 .../DataContextGraphAction.java}                   |  26 +-
 .../cayenne/access/DataContextQueryAction.java     | 375 +++++++++++++++++--
 .../cayenne/util/ObjectContextQueryAction.java     | 416 ---------------------
 4 files changed, 346 insertions(+), 476 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
index 4838db566..7b3298b52 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
@@ -57,7 +57,6 @@ import org.apache.cayenne.runtime.CayenneRuntime;
 import org.apache.cayenne.tx.TransactionFactory;
 import org.apache.cayenne.util.EventUtil;
 import org.apache.cayenne.util.GenericResponse;
-import org.apache.cayenne.util.ObjectContextGraphAction;
 import org.apache.cayenne.util.Util;
 
 import java.io.IOException;
@@ -130,7 +129,7 @@ public class DataContext implements ObjectContext {
      *
      * @since 3.1
      */
-    protected ObjectContextGraphAction graphAction;
+    protected DataContextGraphAction graphAction;
 
     /**
      * Stores user defined properties associated with this DataContext.
@@ -166,7 +165,7 @@ public class DataContext implements ObjectContext {
      */
     public DataContext(DataChannel channel, ObjectStore objectStore) {
 
-        graphAction = new ObjectContextGraphAction(this);
+        graphAction = new DataContextGraphAction(this);
         objectCreator = new DataContextObjectCreator(this);
 
         // inject self as parent context
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextGraphAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataContextGraphAction.java
similarity index 89%
rename from 
cayenne/src/main/java/org/apache/cayenne/util/ObjectContextGraphAction.java
rename to 
cayenne/src/main/java/org/apache/cayenne/access/DataContextGraphAction.java
index fc31f84d2..1f0943202 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextGraphAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/DataContextGraphAction.java
@@ -16,9 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.util;
-
-import java.io.Serializable;
+package org.apache.cayenne.access;
 
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistenceState;
@@ -28,19 +26,14 @@ import org.apache.cayenne.reflect.ArcProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.reflect.PropertyDescriptor;
 
-/**
- * A base implementation of a helper class to handle
- * {@link ObjectContext#propertyChanged(org.apache.cayenne.Persistent, String, 
Object, Object)}
- * processing on behalf of an ObjectContext.
- * 
- * @since 3.0
- * TODO: make this non-public! 
- */
-public class ObjectContextGraphAction implements Serializable {
+import java.io.Serializable;
+
 
-    protected ObjectContext context;
+class DataContextGraphAction implements Serializable {
 
-    public ObjectContextGraphAction(ObjectContext context) {
+    private final ObjectContext context;
+
+    public DataContextGraphAction(ObjectContext context) {
         this.context = context;
     }
 
@@ -64,8 +57,7 @@ public class ObjectContextGraphAction implements Serializable 
{
 
         if (property instanceof ArcProperty) {
             handleArcPropertyChange(object, (ArcProperty) property, oldValue, 
newValue);
-        }
-        else {
+        } else {
             handleSimplePropertyChange(object, propertyName, oldValue, 
newValue);
         }
     }
@@ -99,7 +91,7 @@ public class ObjectContextGraphAction implements Serializable 
{
             String propertyName,
             Object oldValue,
             Object newValue) {
-        
+
         context.getGraphManager().nodePropertyChanged(
                 object.getObjectId(),
                 propertyName,
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
index 4222daa9f..39fa7b253 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
@@ -20,44 +20,175 @@
 package org.apache.cayenne.access;
 
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.Persistent;
+import org.apache.cayenne.QueryResponse;
+import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.cache.QueryCacheEntryFactory;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityInheritanceTree;
 import org.apache.cayenne.query.EntityResultSegment;
+import org.apache.cayenne.query.IteratedQueryDecorator;
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryCacheStrategy;
+import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.query.RefreshQuery;
+import org.apache.cayenne.query.RelationshipQuery;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.util.GenericResponse;
 import org.apache.cayenne.util.ListResponse;
-import org.apache.cayenne.util.ObjectContextQueryAction;
+import org.apache.cayenne.util.ShallowMergeOperation;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
-/**
- * A DataContext-specific version of
- * {@link org.apache.cayenne.util.ObjectContextQueryAction}.
- * 
- * @since 1.2
- */
-class DataContextQueryAction extends ObjectContextQueryAction {
+class DataContextQueryAction {
 
-    protected DataContext actingDataContext;
+    private static final boolean DONE = true;
 
-    public DataContextQueryAction(DataContext actingContext, ObjectContext 
targetContext,
-            Query query) {
-        super(actingContext, targetContext, query);
-        actingDataContext = actingContext;
+    private final ObjectContext actingContext;
+    private final ObjectContext targetContext;
+    private final DataContext actingDataContext;
+    private final Query query;
+    private final QueryMetadata metadata;
+    private final boolean queryOriginator;
+
+    private transient QueryResponse response;
+
+    public DataContextQueryAction(DataContext actingContext, ObjectContext 
targetContext, Query query) {
+
+        this.actingContext = actingContext;
+        this.actingDataContext = actingContext;
+        this.query = query;
+
+        // this means that a caller must pass self as both acting context and 
target
+        // context to indicate that a query originated here... null (ROP) or 
differing
+        // context indicates that the query was originated elsewhere, which has
+        // consequences in LOCAL_CACHE handling
+        this.queryOriginator = targetContext != null && targetContext == 
actingContext;
+
+        // no special target context and same target context as acting context 
mean the
+        // same thing. "normalize" the internal state to avoid confusion
+        this.targetContext = targetContext != actingContext ? targetContext : 
null;
+        this.metadata = query.getMetaData(actingContext.getEntityResolver());
     }
 
-    @Override
-    protected boolean interceptInternalQuery() {
-        return interceptObjectFromDataRowsQuery();
+    /**
+     * Worker method that performs internal query.
+     */
+    public QueryResponse execute() {
+        if (interceptIteratedQuery() != DONE) {
+            if (interceptOIDQuery() != DONE) {
+                if (interceptRelationshipQuery() != DONE) {
+                    if (interceptRefreshQuery() != DONE) {
+                        if (interceptLocalCache() != DONE) {
+                            executePostCache();
+                        }
+                    }
+                }
+            }
+        }
+
+        interceptObjectConversion();
+        return response;
     }
 
-    private boolean interceptObjectFromDataRowsQuery() {
+    private boolean interceptIteratedQuery() {
+        if (query instanceof IteratedQueryDecorator) {
+            runQuery();
+            return DONE;
+        }
+        return !DONE;
+    }
+
+    private void executePostCache() {
+        if (interceptInternalQuery() != DONE) {
+            if (interceptPaginatedQuery() != DONE) {
+                runQuery();
+            }
+        }
+    }
+
+    /**
+     * Transfers fetched objects into the target context if it is different 
from "acting"
+     * context. Note that when this method is invoked, result objects are 
already
+     * registered with acting context by the parent channel.
+     */
+    protected void interceptObjectConversion() {
+
+        if (targetContext != null && !metadata.isFetchingDataRows()) {
+
+            // rewrite response to contain objects from the query context
+
+            GenericResponse childResponse = new GenericResponse();
+            ShallowMergeOperation merger = null;
+
+            for (response.reset(); response.next(); ) {
+                if (response.isList()) {
+                    List<?> objects = response.currentList();
+                    if (objects.isEmpty()) {
+                        childResponse.addResultList(objects);
+                    } else {
+
+                        // minor optimization, skip Object[] if there are no 
persistent objects
+                        boolean haveObjects = metadata.getResultSetMapping() 
== null;
+                        if (!haveObjects) {
+                            for (Object next : metadata.getResultSetMapping()) 
{
+                                if (next instanceof EntityResultSegment) {
+                                    haveObjects = true;
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (merger == null) {
+                            merger = new ShallowMergeOperation(targetContext);
+                        }
+
+                        // TODO: Andrus 1/31/2006 - IncrementalFaultList is 
not properly
+                        // transferred between contexts....
+
+                        List<Object> childObjects = new 
ArrayList<>(objects.size());
+                        for (Object object1 : objects) {
+                            if (object1 instanceof Persistent object) {
+                                childObjects.add(merger.merge(object));
+                            } else if (haveObjects && object1 instanceof 
Object[] parentData) {
+                                // merge objects inside Object[]
+                                Object[] childData = new 
Object[parentData.length];
+                                System.arraycopy(parentData, 0, childData, 0, 
parentData.length);
+                                for (int i = 0; i < childData.length; i++) {
+                                    if (childData[i] instanceof Persistent) {
+                                        childData[i] = 
merger.merge((Persistent) childData[i]);
+                                    }
+                                }
+                                childObjects.add(childData);
+                            } else {
+                                childObjects.add(object1);
+                            }
+                        }
+
+                        childResponse.addResultList(childObjects);
+                    }
+                } else {
+                    
childResponse.addBatchUpdateCount(response.currentUpdateCount());
+                }
+            }
+
+            response = childResponse;
+        }
+
+    }
+
+    protected boolean interceptInternalQuery() {
         if (query instanceof ObjectsFromDataRowsQuery 
objectsFromDataRowsQuery) {
             response = new ListResponse(actingDataContext.objectsFromDataRows(
                     objectsFromDataRowsQuery.getDescriptor(),
@@ -68,9 +199,8 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
     }
 
     /**
-     * Overrides super implementation to property handle data row fetches.
+     * Handles {@link ObjectIdQuery}, properly handling data row fetches.
      */
-    @Override
     protected boolean interceptOIDQuery() {
         if (query instanceof ObjectIdQuery oidQuery) {
 
@@ -79,10 +209,10 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
                 if (object != null) {
 
                     // TODO: andrus, 10/14/2006 - obtaining a row from an 
object is the
-                    // only piece that makes this method different from the 
super
-                    // implementation. This is used in NEW objects sorting on 
insert. It
-                    // would be nice to implement an alternative algorithm 
that wouldn't
-                    // require this hack.
+                    // only piece that makes this method different from a 
plain cache
+                    // lookup. This is used in NEW objects sorting on insert. 
It would be
+                    // nice to implement an alternative algorithm that 
wouldn't require
+                    // this hack.
                     if (oidQuery.isFetchingDataRows()) {
                         object = 
actingDataContext.currentSnapshot((Persistent) object);
                     }
@@ -100,7 +230,116 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
         return !DONE;
     }
 
-    @Override
+    // TODO: bunch of copy/paset from DataDomainQueryAction
+    protected Object polymorphicObjectFromCache(ObjectId superOid) {
+        Object object = actingContext.getGraphManager().getNode(superOid);
+        if (object != null) {
+            return object;
+        }
+
+        EntityInheritanceTree inheritanceTree = 
actingContext.getEntityResolver().getInheritanceTree(superOid.getEntityName());
+        if (!inheritanceTree.getChildren().isEmpty()) {
+            object = polymorphicObjectFromCache(inheritanceTree, superOid);
+        }
+
+        return object;
+    }
+
+    private Object polymorphicObjectFromCache(EntityInheritanceTree superNode, 
ObjectId superOid) {
+
+        for (EntityInheritanceTree child : superNode.getChildren()) {
+            ObjectId id = ObjectId.of(child.getEntity().getName(), superOid);
+            Object object = actingContext.getGraphManager().getNode(id);
+            if (object != null) {
+                return object;
+            }
+
+            object = polymorphicObjectFromCache(child, superOid);
+            if (object != null) {
+                return object;
+            }
+        }
+
+        return null;
+    }
+
+    protected boolean interceptRelationshipQuery() {
+
+        if (query instanceof RelationshipQuery relationshipQuery) {
+            if (!relationshipQuery.isRefreshing()) {
+
+                // don't intercept to-many relationships if fetch is done to 
the same
+                // context as the root context of this action - this will 
result in an
+                // infinite loop.
+
+                if (targetContext == null
+                        && relationshipQuery.getRelationship(
+                        actingContext.getEntityResolver()).isToMany()) {
+                    return !DONE;
+                }
+
+                ObjectId id = relationshipQuery.getObjectId();
+                Object object = actingContext.getGraphManager().getNode(id);
+
+                if (object != null) {
+
+                    ClassDescriptor descriptor = actingContext
+                            .getEntityResolver()
+                            .getClassDescriptor(id.getEntityName());
+
+                    if (!descriptor.isFault(object)) {
+
+                        ArcProperty property = (ArcProperty) descriptor
+                                
.getProperty(relationshipQuery.getRelationshipName());
+
+                        if (!property.isFault(object)) {
+
+                            Object related = 
property.readPropertyDirectly(object);
+
+                            // null to-one
+                            List<?> result = switch (related) {
+                                case null -> new ArrayList<>(1);
+
+                                // to-many List
+                                case List list -> list;
+
+                                // to-many Set
+                                case Set set -> new ArrayList<>(set);
+
+                                // to-many Map
+                                case Map map -> new ArrayList<>(map.values());
+
+                                // non-null to-one
+                                // TODO: any risks of returning an immutable 
list here?
+                                default -> Collections.singletonList(related);
+                            };
+
+                            this.response = new ListResponse(result);
+                            return DONE;
+
+                        }
+
+                        // Workaround for CAY-1183. If a Relationship query is 
being sent
+                        // from child context, we assure that local object is 
not NEW and
+                        // relationship - unresolved (this way exception will 
occur). This
+                        // helps when faulting objects that were committed to 
parent
+                        // context (this), but not to database. Checking type 
of context's
+                        // channel is the only way to ensure that we are on 
the top level
+                        // of context hierarchy (there might be more than 
one-level-deep
+                        // nested contexts).
+                        if (((Persistent) object).getPersistenceState() == 
PersistenceState.NEW
+                                && !(actingContext.getParent() instanceof 
ObjectContext)) {
+                            this.response = new ListResponse();
+                            return DONE;
+                        }
+                    }
+                }
+            }
+        }
+
+        return !DONE;
+    }
+
     protected boolean interceptPaginatedQuery() {
         if (metadata.getPageSize() > 0) {
             // this will select raw ids
@@ -121,7 +360,7 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
     private IncrementalFaultList<?> createIncrementalFaultList(List<?> rawIds, 
int maxIdQualifierSize) {
         // just a sanity check
         Objects.requireNonNull(rawIds, "Trying to execute paginated query that 
is not a select query");
-        if(isMixedResultsForPaginatedQuery()) {
+        if (isMixedResultsForPaginatedQuery()) {
             return new MixedResultIncrementalFaultList<>(actingDataContext, 
query, maxIdQualifierSize, rawIds);
         } else {
             DbEntity dbEntity = metadata.getDbEntity();
@@ -136,10 +375,10 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
     private boolean isMixedResultsForPaginatedQuery() {
         boolean mixedResults = false;
         List<Object> rsMapping = metadata.getResultSetMapping();
-        if(rsMapping != null) {
-            if(rsMapping.size() > 1) {
+        if (rsMapping != null) {
+            if (rsMapping.size() > 1) {
                 mixedResults = true;
-            } else if(rsMapping.size() == 1) {
+            } else if (rsMapping.size() == 1) {
                 mixedResults = !(rsMapping.getFirst() instanceof 
EntityResultSegment)
                         || !metadata.isSingleResultSetMapping();
             }
@@ -147,24 +386,21 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
         return mixedResults;
     }
 
-    @Override
     protected boolean interceptRefreshQuery() {
         if (query instanceof RefreshQuery refreshQuery) {
 
-            DataContext context = (DataContext) actingContext;
-
             // handle four separate cases, but do not combine them as it will 
be
             // unclear how to handle cascading behavior
 
             // 1. refresh all
             if (refreshQuery.isRefreshAll()) {
-                synchronized (context.getObjectStore()) {
+                synchronized (actingDataContext.getObjectStore()) {
 
-                    invalidateLocally(context.getObjectStore(), context
+                    invalidateLocally(actingDataContext.getObjectStore(), 
actingDataContext
                             .getObjectStore()
                             .getObjectIterator());
 
-                    context.getQueryCache().clear();
+                    actingDataContext.getQueryCache().clear();
                 }
 
                 // cascade
@@ -175,8 +411,8 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
             Collection<?> objects = refreshQuery.getObjects();
             if (objects != null && !objects.isEmpty()) {
 
-                synchronized (context.getObjectStore()) {
-                    invalidateLocally(context.getObjectStore(), 
objects.iterator());
+                synchronized (actingDataContext.getObjectStore()) {
+                    invalidateLocally(actingDataContext.getObjectStore(), 
objects.iterator());
                 }
 
                 // cascade
@@ -188,11 +424,11 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
             if (cachedQuery != null) {
 
                 String cacheKey = cachedQuery
-                        .getMetaData(context.getEntityResolver())
+                        .getMetaData(actingDataContext.getEntityResolver())
                         .getCacheKey();
-                context.getQueryCache().remove(cacheKey);
+                actingDataContext.getQueryCache().remove(cacheKey);
 
-                this.response = context.performGenericQuery(cachedQuery);
+                this.response = 
actingDataContext.performGenericQuery(cachedQuery);
 
                 // do not cascade to avoid running query twice
                 return DONE;
@@ -203,7 +439,7 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
             if (groups != null && groups.length > 0) {
 
                 for (String group : groups) {
-                    context.getQueryCache().removeGroup(group);
+                    actingDataContext.getQueryCache().removeGroup(group);
                 }
 
                 // cascade group invalidation
@@ -240,4 +476,63 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
             object.setPersistenceState(PersistenceState.HOLLOW);
         }
     }
+
+    protected boolean interceptLocalCache() {
+
+        if (metadata.getCacheKey() == null) {
+            return !DONE;
+        }
+
+        // ignore local cache unless this context originated the query...
+        if (!queryOriginator) {
+            return !DONE;
+        }
+
+        boolean cache = QueryCacheStrategy.LOCAL_CACHE == 
metadata.getCacheStrategy();
+        boolean cacheOrCacheRefresh = cache
+                || QueryCacheStrategy.LOCAL_CACHE_REFRESH == 
metadata.getCacheStrategy();
+
+        if (!cacheOrCacheRefresh) {
+            return !DONE;
+        }
+
+        QueryCache queryCache = getQueryCache();
+        QueryCacheEntryFactory factory = getCacheObjectFactory();
+
+        if (cache) {
+            boolean wasResponseNull = (response == null);
+            List cachedResults = queryCache.get(metadata, factory);
+
+            // response may already be initialized by the factory above ... it 
is null if
+            // there was a preexisting cache entry
+            if (response == null || wasResponseNull) {
+                response = new ListResponse(cachedResults);
+            }
+        } else {
+            // on cache-refresh request, fetch without blocking and fill the 
cache
+            queryCache.put(metadata, factory.createObject());
+        }
+
+        return DONE;
+    }
+
+    protected QueryCache getQueryCache() {
+        return actingDataContext.getQueryCache();
+    }
+
+    protected QueryCacheEntryFactory getCacheObjectFactory() {
+        return () -> {
+            executePostCache();
+            List result = response.firstList();
+            // make an immutable list to make sure callers don't mess it up
+            return result != null ? Collections.unmodifiableList(result) : 
null;
+        };
+    }
+
+    /**
+     * Fetches data from the channel.
+     */
+    protected void runQuery() {
+        this.response = actingContext.getParent().onQuery(actingContext, 
query);
+    }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java 
b/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java
deleted file mode 100644
index 05cbed1d1..000000000
--- 
a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://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.util;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.PersistenceState;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.QueryResponse;
-import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.cache.QueryCache;
-import org.apache.cayenne.cache.QueryCacheEntryFactory;
-import org.apache.cayenne.map.EntityInheritanceTree;
-import org.apache.cayenne.query.*;
-import org.apache.cayenne.reflect.ArcProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-
-/**
- * A helper class that implements
- * {@link org.apache.cayenne.DataChannel#onQuery(ObjectContext, Query)} logic 
on behalf of
- * an ObjectContext.
- * <p>
- * <i>Intended for internal use only.</i>
- * </p>
- * 
- * @since 1.2
- */
-public abstract class ObjectContextQueryAction {
-
-    protected static final boolean DONE = true;
-
-    protected ObjectContext targetContext;
-    protected ObjectContext actingContext;
-    protected Query query;
-    protected QueryMetadata metadata;
-    protected boolean queryOriginator;
-
-    protected transient QueryResponse response;
-
-    public ObjectContextQueryAction(ObjectContext actingContext,
-            ObjectContext targetContext, Query query) {
-
-        this.actingContext = actingContext;
-        this.query = query;
-
-        // this means that a caller must pass self as both acting context and 
target
-        // context to indicate that a query originated here... null (ROP) or 
differing
-        // context indicates that the query was originated elsewhere, which has
-        // consequences in LOCAL_CACHE handling
-        this.queryOriginator = targetContext != null && targetContext == 
actingContext;
-
-        // no special target context and same target context as acting context 
mean the
-        // same thing. "normalize" the internal state to avoid confusion
-        this.targetContext = targetContext != actingContext ? targetContext : 
null;
-        this.metadata = query.getMetaData(actingContext.getEntityResolver());
-    }
-
-    /**
-     * Worker method that performs internal query.
-     */
-    public QueryResponse execute() {
-        if (interceptIteratedQuery() != DONE) {
-            if (interceptOIDQuery() != DONE) {
-                if (interceptRelationshipQuery() != DONE) {
-                    if (interceptRefreshQuery() != DONE) {
-                        if (interceptLocalCache() != DONE) {
-                            executePostCache();
-                        }
-                    }
-                }
-            }
-        }
-
-        interceptObjectConversion();
-        return response;
-    }
-
-    private boolean interceptIteratedQuery() {
-        if (query instanceof IteratedQueryDecorator) {
-            runQuery();
-            return DONE;
-        }
-        return !DONE;
-    }
-
-    private void executePostCache() {
-        if (interceptInternalQuery() != DONE) {
-            if (interceptPaginatedQuery() != DONE) {
-                runQuery();
-            }
-        }
-    }
-
-    /**
-     * Transfers fetched objects into the target context if it is different 
from "acting"
-     * context. Note that when this method is invoked, result objects are 
already
-     * registered with acting context by the parent channel.
-     */
-    protected void interceptObjectConversion() {
-
-        if (targetContext != null && !metadata.isFetchingDataRows()) {
-
-            // rewrite response to contain objects from the query context
-
-            GenericResponse childResponse = new GenericResponse();
-            ShallowMergeOperation merger = null;
-
-            for (response.reset(); response.next();) {
-                if (response.isList()) {
-                    List objects = response.currentList();
-                    if (objects.isEmpty()) {
-                        childResponse.addResultList(objects);
-                    } else {
-
-                        // minor optimization, skip Object[] if there are no 
persistent objects
-                        boolean haveObjects = metadata.getResultSetMapping() 
== null;
-                        if(!haveObjects) {
-                            for (Object next : metadata.getResultSetMapping()) 
{
-                                if(next instanceof EntityResultSegment) {
-                                    haveObjects = true;
-                                    break;
-                                }
-                            }
-                        }
-
-                        if (merger == null) {
-                            merger = new ShallowMergeOperation(targetContext);
-                        }
-
-                        // TODO: Andrus 1/31/2006 - IncrementalFaultList is 
not properly
-                        // transferred between contexts....
-
-                        List<Object> childObjects = new 
ArrayList<>(objects.size());
-                        for (Object object1 : objects) {
-                            if(object1 instanceof Persistent) {
-                                Persistent object = (Persistent) object1;
-                                childObjects.add(merger.merge(object));
-                            } else if(haveObjects && object1 instanceof 
Object[]) {
-                                // merge objects inside Object[]
-                                Object[] parentData = (Object[]) object1;
-                                Object[] childData = new 
Object[parentData.length];
-                                System.arraycopy(parentData, 0, childData, 0, 
parentData.length);
-                                for(int i=0; i<childData.length; i++) {
-                                    if(childData[i] instanceof Persistent) {
-                                        childData[i] = 
merger.merge((Persistent)childData[i]);
-                                    }
-                                }
-                                childObjects.add(childData);
-                            } else {
-                                childObjects.add(object1);
-                            }
-                        }
-
-                        childResponse.addResultList(childObjects);
-                    }
-                }
-                else {
-                    
childResponse.addBatchUpdateCount(response.currentUpdateCount());
-                }
-            }
-
-            response = childResponse;
-        }
-
-    }
-
-    protected boolean interceptInternalQuery() {
-        return !DONE;
-    }
-
-    protected boolean interceptOIDQuery() {
-        if (query instanceof ObjectIdQuery) {
-            ObjectIdQuery oidQuery = (ObjectIdQuery) query;
-
-            if (!oidQuery.isFetchMandatory() && 
!oidQuery.isFetchingDataRows()) {
-                Object object = polymorphicObjectFromCache(
-                        oidQuery.getObjectId());
-                if (object != null) {
-
-                    // do not return hollow objects
-                    if (((Persistent) object).getPersistenceState() == 
PersistenceState.HOLLOW) {
-                        return !DONE;
-                    }
-
-                    this.response = new ListResponse(object);
-                    return DONE;
-                }
-            }
-        }
-
-        return !DONE;
-    }
-    
-    // TODO: bunch of copy/paset from DataDomainQueryAction
-    protected Object polymorphicObjectFromCache(ObjectId superOid) {
-               Object object = 
actingContext.getGraphManager().getNode(superOid);
-               if (object != null) {
-                       return object;
-               }
-
-               EntityInheritanceTree inheritanceTree = 
actingContext.getEntityResolver().getInheritanceTree(superOid.getEntityName());
-               if (!inheritanceTree.getChildren().isEmpty()) {
-                       object = polymorphicObjectFromCache(inheritanceTree, 
superOid);
-               }
-
-               return object;
-       }
-    
-       private Object polymorphicObjectFromCache(EntityInheritanceTree 
superNode, ObjectId superOid) {
-
-               for (EntityInheritanceTree child : superNode.getChildren()) {
-                       ObjectId id = ObjectId.of(child.getEntity().getName(), 
superOid);
-                       Object object = 
actingContext.getGraphManager().getNode(id);
-                       if (object != null) {
-                               return object;
-                       }
-                       
-                       object = polymorphicObjectFromCache(child, superOid);
-                       if (object != null) {
-                               return object;
-                       }
-               }
-
-               return null;
-       }
-
-    protected boolean interceptRelationshipQuery() {
-
-        if (query instanceof RelationshipQuery) {
-            RelationshipQuery relationshipQuery = (RelationshipQuery) query;
-            if (!relationshipQuery.isRefreshing()) {
-
-                // don't intercept to-many relationships if fetch is done to 
the same
-                // context as the root context of this action - this will 
result in an
-                // infinite loop.
-
-                if (targetContext == null
-                        && relationshipQuery.getRelationship(
-                                actingContext.getEntityResolver()).isToMany()) 
{
-                    return !DONE;
-                }
-
-                ObjectId id = relationshipQuery.getObjectId();
-                Object object = actingContext.getGraphManager().getNode(id);
-
-                if (object != null) {
-
-                    ClassDescriptor descriptor = actingContext
-                            .getEntityResolver()
-                            .getClassDescriptor(id.getEntityName());
-
-                    if (!descriptor.isFault(object)) {
-
-                        ArcProperty property = (ArcProperty) descriptor
-                                
.getProperty(relationshipQuery.getRelationshipName());
-
-                        if (!property.isFault(object)) {
-
-                            Object related = 
property.readPropertyDirectly(object);
-
-                            List result;
-
-                            // null to-one
-                            if (related == null) {
-                                result = new ArrayList(1);
-                            }
-                            // to-many List
-                            else if (related instanceof List) {
-                                result = (List) related;
-                            }
-                            // to-many Set
-                            else if (related instanceof Set) {
-                                result = new ArrayList((Set) related);
-                            }
-                            // to-many Map
-                            else if (related instanceof Map) {
-                                result = new ArrayList(((Map) 
related).values());
-                            }
-                            // non-null to-one
-                            else {
-                                result = new ArrayList(1);
-                                result.add(related);
-                            }
-
-                            this.response = new ListResponse(result);
-                            return DONE;
-
-                        }
-
-                        /**
-                         * Workaround for CAY-1183. If a Relationship query is 
being sent
-                         * from child context, we assure that local object is 
not NEW and
-                         * relationship - unresolved (this way exception will 
occur). This
-                         * helps when faulting objects that were committed to 
parent
-                         * context (this), but not to database. Checking type 
of context's
-                         * channel is the only way to ensure that we are on 
the top level
-                         * of context hierarchy (there might be more than 
one-level-deep
-                         * nested contexts).
-                         */
-                        if (((Persistent) object).getPersistenceState() == 
PersistenceState.NEW
-                                && !(actingContext.getParent() instanceof 
ObjectContext)) {
-                            this.response = new ListResponse();
-                            return DONE;
-                        }
-                    }
-                }
-            }
-        }
-
-        return !DONE;
-    }
-
-    /**
-     * @since 3.0
-     */
-    protected abstract boolean interceptPaginatedQuery();
-
-    /**
-     * @since 3.0
-     */
-    protected abstract boolean interceptRefreshQuery();
-
-    /**
-     * @since 3.0
-     */
-    protected boolean interceptLocalCache() {
-
-        if (metadata.getCacheKey() == null) {
-            return !DONE;
-        }
-
-        // ignore local cache unless this context originated the query...
-        if (!queryOriginator) {
-            return !DONE;
-        }
-
-        boolean cache = QueryCacheStrategy.LOCAL_CACHE == 
metadata.getCacheStrategy();
-        boolean cacheOrCacheRefresh = cache
-                || QueryCacheStrategy.LOCAL_CACHE_REFRESH == 
metadata.getCacheStrategy();
-
-        if (!cacheOrCacheRefresh) {
-            return !DONE;
-        }
-
-        QueryCache queryCache = getQueryCache();
-        QueryCacheEntryFactory factory = getCacheObjectFactory();
-
-        if (cache) {
-            boolean wasResponseNull = (response == null);
-            List cachedResults = queryCache.get(metadata, factory);
-
-            // response may already be initialized by the factory above ... it 
is null if
-            // there was a preexisting cache entry
-            if (response == null || wasResponseNull) {
-                response = new ListResponse(cachedResults);
-            }
-        } else {
-            // on cache-refresh request, fetch without blocking and fill the 
cache
-            queryCache.put(metadata, factory.createObject());
-        }
-
-        return DONE;
-    }
-
-    /**
-     * @since 3.0
-     */
-    protected QueryCache getQueryCache() {
-        return ((DataContext) actingContext).getQueryCache();
-    }
-
-    /**
-     * @since 3.0
-     */
-    protected QueryCacheEntryFactory getCacheObjectFactory() {
-        return new QueryCacheEntryFactory() {
-
-            public List createObject() {
-                executePostCache();
-                List result = response.firstList();
-                // make an immutable list to make sure callers don't mess it up
-                return result != null ? Collections.unmodifiableList(result) : 
null;
-            }
-        };
-    }
-
-    /**
-     * Fetches data from the channel.
-     */
-    protected void runQuery() {
-        this.response = actingContext.getParent().onQuery(actingContext, 
query);
-    }
-}


Reply via email to