Bug 436871 - Fixed NPE in checkForCustomQuery (LRG shall pass now)
Signed-off-by: Tomas Kraus <tomas.kraus@oracle.com>
Reviewed-by: Lukas Jungmann <lukas.jungmann@oracle.com>, Martin Grebac <martin.grebac@oracle.com>
diff --git a/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/ObjectLevelReadQueryTest.java b/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/ObjectLevelReadQueryTest.java
new file mode 100644
index 0000000..833fbca
--- /dev/null
+++ b/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/ObjectLevelReadQueryTest.java
@@ -0,0 +1,356 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ *    04/20/2015-2.7 Tomas Kraus - Concurrency test to trigger NPE in
+ *                                 checkForCustomQuery(AbstractSession, AbstractRecord).
+ ******************************************************************************/
+package org.eclipse.persistence.testing.tests.queries;
+
+import static org.eclipse.persistence.logging.SessionLog.INFO;
+import static org.eclipse.persistence.logging.SessionLog.WARNING;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.persistence.internal.sessions.AbstractRecord;
+import org.eclipse.persistence.internal.sessions.AbstractSession;
+import org.eclipse.persistence.logging.SessionLog;
+import org.eclipse.persistence.queries.DatabaseQuery;
+import org.eclipse.persistence.queries.ObjectLevelReadQuery;
+import org.eclipse.persistence.queries.ReadAllQuery;
+import org.eclipse.persistence.queries.ReadObjectQuery;
+import org.eclipse.persistence.queries.SQLCall;
+import org.eclipse.persistence.sessions.Session;
+import org.eclipse.persistence.testing.framework.TestCase;
+import org.eclipse.persistence.testing.models.employee.domain.Employee;
+
+/**
+ * Test read queries using objects.
+ */
+public abstract class ObjectLevelReadQueryTest extends TestCase {
+
+    /** Fail message for NPE in checkForCustomQuery method. */
+    private static final String FAIL_NPE
+        = "NullPointerException thrown in checkForCustomQuery(AbstractSession, AbstractRecord)";
+
+    /** Logger used in test. */
+    protected SessionLog log;
+
+    /** Current database session. */
+    protected Session session;
+
+    /** Some entity to be used in query. */
+    protected final Class entity = Employee.class;
+
+    /** Some SQL call to be used in query. */
+    protected final SQLCall call = new SQLCall("SELECT t0.EMP_ID FROM EMPLOYEE t0");
+
+    /**
+     * Creates an instance of jUnit tests for read queries using objects.
+     * @param name jUnit test name.
+     */
+    public ObjectLevelReadQueryTest() {
+        super();
+    }
+
+    /**
+     * Creates an instance of jUnit tests for read queries using objects.
+     * @param name jUnit test name.
+     */
+    public ObjectLevelReadQueryTest(final String name) {
+        super();
+        setName(name);
+    }
+
+    /**
+     * Initialize this test suite.
+     */
+    public void setup() {
+        session = getSession();
+        log = session.getSessionLog();
+    }
+
+    /**
+     * Clean this test suite.
+     */
+    public void reset() {
+        session = null;
+        log = null;
+    }
+
+    /**
+     * {@link TestCase#test()} method overwritten as public to avoid failures.
+     */
+    @Override
+    public abstract void test() throws Throwable;
+
+    /**
+     * {@code [ObjectLevelReadQuery].isCustomQueryUsed} value changer
+     * for {@link #testCheckForCustomQueryRaceConditions()}.
+     */
+    private static final class ValueChanger extends Thread {
+
+        /** Logger used in test. */
+        private final SessionLog log;
+
+        /** Query where to change {@code isCustomQueryUsed} value. */
+        private final ObjectLevelReadQuery query;
+
+        /** Instance used for synchronization. */
+        private final Object mutex;
+
+        /** [ObjectLevelReadQuery].isCustomQueryUsed} field. */
+        private final Field isCustomQueryUsed;
+
+        /** Thread execution flag. */
+        private boolean execute;
+
+        /**
+         * Creates an instance of value changer thread.
+         * @param objectLevelReadQuery Query object where {@code isCustomQueryUsed} value will be changed.
+         * @param syncMutex            Instance used for synchronization.
+         * @param sessionLog           Logger used in test.
+         */
+        private ValueChanger(final ObjectLevelReadQuery objectLevelReadQuery, final Object syncMutex,
+                final SessionLog sessionLog) {
+            log = sessionLog;
+            // Calling run() directly will do nothing.
+            query = objectLevelReadQuery;
+            mutex = syncMutex;
+            execute = false;
+            // Placeholder for initialization.
+            Field isCustomQueryUsedLocal;
+            try {
+                isCustomQueryUsedLocal = DatabaseQuery.class.getDeclaredField("isCustomQueryUsed");
+            } catch (SecurityException e) {
+                log.logThrowable(WARNING, e);
+                isCustomQueryUsedLocal = null;
+            } catch (NoSuchFieldException e) {
+                log.logThrowable(WARNING, e);
+                isCustomQueryUsedLocal = null;
+            }
+            if (isCustomQueryUsedLocal != null) {
+                try {
+                    isCustomQueryUsedLocal.setAccessible(true);
+                } catch (SecurityException e) {
+                    log.logThrowable(WARNING, e);
+                    isCustomQueryUsedLocal = null;
+                }
+            }
+            isCustomQueryUsed = isCustomQueryUsedLocal;
+        }
+
+        /**
+         * Check if value changer initialization was successful.
+         * @return Value of {@code true} when {@code isCustomQueryUsed} field is available or {@code false} otherwise.
+         */
+        private boolean isOk() {
+            return isCustomQueryUsed != null;
+        }
+
+        /**
+         * Start thread execution.
+         */
+        public void start() {
+            execute = true;
+            super.start();
+        }
+
+        /**
+         * Request thread to finish.
+         */
+        public void finish() {
+            execute = false;
+        }
+
+        /**
+         * Thread code to be executed.
+         */
+        @Override
+        public void run() {
+            synchronized(mutex) {
+                mutex.notify();
+            }
+            while(execute) {
+                try {
+                    isCustomQueryUsed.set(query, Boolean.TRUE);
+                    isCustomQueryUsed.set(query, (Boolean)null);
+                } catch (IllegalArgumentException e) {
+                    log.logThrowable(WARNING, e);
+                    execute = false;
+                } catch (IllegalAccessException e) {
+                    log.logThrowable(WARNING, e);
+                    execute = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Get {@code [ObjectLevelReadQuery].checkForCustomQuery(AbstractSession, AbstractRecord)} method accessor.
+     * @param c Class where to search for method.
+     * @return {@code [ObjectLevelReadQuery].checkForCustomQuery(AbstractSession, AbstractRecord)} method accessor.
+     */
+    protected Method getCheckForCustomQueryMethod(final Class c) {
+        Method method;
+        try {
+            method = c.getDeclaredMethod(
+                    "checkForCustomQuery", AbstractSession.class, AbstractRecord.class);
+            method.setAccessible(true);
+        } catch (NoSuchMethodException e) {
+            log.logThrowable(WARNING, e);
+            method = null;
+        } catch (SecurityException e) {
+            log.logThrowable(WARNING, e);
+            method = null;
+        }
+        return method;
+    }
+
+    /**
+     * Call {@code [ObjectLevelReadQuery].checkForCustomQuery(AbstractSession, AbstractRecord)} method.
+     * @param instance ObjectLevelReadQuery instance on which method is called.
+     * @param mathod   {@code [ObjectLevelReadQuery].checkForCustomQuery(AbstractSession, AbstractRecord)}
+     *                 method accessor.
+     * @throws Throwable when any exception except NPE in invoked method occurs.
+     */
+    protected DatabaseQuery callCheckForCustomQueryMethod(final Object instance, final Method method) throws Throwable {
+        DatabaseQuery databaseQuery;
+        try {
+            databaseQuery = (DatabaseQuery)method.invoke(instance, session, (AbstractRecord)null);
+        } catch (InvocationTargetException e) {
+            if (e.getTargetException() instanceof NullPointerException) {
+                log.log(WARNING, FAIL_NPE);
+                e.printStackTrace();
+                fail(FAIL_NPE);
+            }
+            log.logThrowable(WARNING, e);
+            throw e;
+        } catch (Throwable e) {
+            log.logThrowable(WARNING, e);
+            e.printStackTrace();
+            throw e;
+        }
+        return databaseQuery;
+    }
+
+    /**
+     * jUnit test to verify NPE in {@code checkForCustomQuery} method in {@link ReadAllQuery} class.
+     */
+    public static final class CustomQueryRaceConditionsInReadAllQueryTest extends ObjectLevelReadQueryTest {
+
+        /**
+         * Creates an instance of jUnit test.
+         */
+        public CustomQueryRaceConditionsInReadAllQueryTest() {
+            super();
+        }
+
+        /**
+         * Creates an instance of jUnit test.
+         * @param name jUnit test name.
+         */
+        public CustomQueryRaceConditionsInReadAllQueryTest(final String name) {
+            super(name);
+        }
+
+        /**
+         * Test {@code [ReadAllQuery].checkForCustomQuery(AbstractSession, AbstractRecord)}
+         * when {@code isCustomQueryUsed} is changed to null during query execution.
+         * Reproduction scenario for bug# 436871 which works with high enough probability.
+         * @throws Throwable when any exception except NPE in checkForCustomQuery occurs.
+         */
+        @Override
+        public void test() throws Throwable {
+            log.log(INFO, "Running checkCustomQueryRaceConditionsInReadAllQuery test.");
+            final Method checkForCustomQuery = getCheckForCustomQueryMethod(ReadAllQuery.class);
+            final ReadAllQuery query = new ReadAllQuery(entity, call);
+            final Object mutex = new Object();
+            final ValueChanger changer = new ValueChanger(query, mutex, log);
+            assertTrue("Initialization of isCustomQueryUsed property value changer failed.", changer.isOk());
+            try {
+                synchronized (mutex) {
+                    changer.start();
+                    mutex.wait();
+                }
+            } catch (InterruptedException e) {
+                log.logThrowable(WARNING, e);
+            }
+            query.setReferenceClass(entity);
+            query.setDescriptor(session.getDescriptor(entity));
+            // 100 iterations is enough to get exception in almost 100% of tests.
+            for (int i = 0; i < 100; i++)
+                callCheckForCustomQueryMethod(query, checkForCustomQuery);
+            changer.finish();
+            try {
+                changer.join();
+            } catch (InterruptedException e) {
+                log.logThrowable(WARNING, e);
+            }
+        }
+    }
+
+    /**
+     * jUnit test to verify NPE in {@code checkForCustomQuery} method in {@link ReadObjectQuery} class.
+     */
+    public static final class CustomQueryRaceConditionsInReadObjectQueryTest extends ObjectLevelReadQueryTest {
+
+        /**
+         * Creates an instance of jUnit test.
+         */
+        public CustomQueryRaceConditionsInReadObjectQueryTest() {
+            super();
+        }
+
+        /**
+         * Creates an instance of jUnit test.
+         * @param name jUnit test name.
+         */
+        public CustomQueryRaceConditionsInReadObjectQueryTest(final String name) {
+            super(name);
+        }
+
+        /**
+         * Test {@code [ReadObjectQuery].checkForCustomQuery(AbstractSession, AbstractRecord)}
+         * when {@code isCustomQueryUsed} is changed to null during query execution.
+         * Reproduction scenario for bug# 436871 which works with high enough probability.
+         * @throws Throwable when any exception except NPE in checkForCustomQuery occurs.
+         */
+        @Override
+        public void test() throws Throwable {
+            log.log(INFO, "Running checkCustomQueryRaceConditionsInReadObjectQuery test.");
+            final Method checkForCustomQuery = getCheckForCustomQueryMethod(ReadObjectQuery.class);
+            final ReadObjectQuery query = new ReadObjectQuery(entity, call);
+            final Object mutex = new Object();
+            final ValueChanger changer = new ValueChanger(query, mutex, log);
+            assertTrue("Initialization of isCustomQueryUsed property value changer failed.", changer.isOk());
+            try {
+                synchronized (mutex) {
+                    changer.start();
+                    mutex.wait();
+                }
+            } catch (InterruptedException e) {
+                log.logThrowable(WARNING, e);
+            }
+            query.setReferenceClass(entity);
+            query.setDescriptor(session.getDescriptor(entity));
+            // 100 iterations is enough to get exception in almost 100% of tests.
+            for (int i = 0; i < 100; i++)
+                callCheckForCustomQueryMethod(query, checkForCustomQuery);
+            changer.finish();
+            try {
+                changer.join();
+            } catch (InterruptedException e) {
+                log.logThrowable(WARNING, e);
+            }
+        }
+    }
+}
diff --git a/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/QueryFrameworkTestSuite.java b/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/QueryFrameworkTestSuite.java
index eb86097..510657d 100644
--- a/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/QueryFrameworkTestSuite.java
+++ b/foundation/eclipselink.core.test/src/org/eclipse/persistence/testing/tests/queries/QueryFrameworkTestSuite.java
@@ -1,30 +1,52 @@
-/*******************************************************************************

- * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.

- * This program and the accompanying materials are made available under the 

- * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 

- * which accompanies this distribution. 

+/*******************************************************************************
+ * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html

  * and the Eclipse Distribution License is available at 

  * http://www.eclipse.org/org/documents/edl-v10.php.

  *

  * Contributors:

  *     Oracle - initial API and implementation from Oracle TopLink

- ******************************************************************************/  

-package org.eclipse.persistence.testing.tests.queries;

-

-import java.util.*;

-import org.eclipse.persistence.testing.models.employee.domain.*;

-import org.eclipse.persistence.testing.models.employee.domain.Project;

-import org.eclipse.persistence.sessions.*;

-import org.eclipse.persistence.expressions.*;

-import org.eclipse.persistence.internal.sessions.*;

-import org.eclipse.persistence.queries.*;

-import org.eclipse.persistence.sessions.Record;

-import org.eclipse.persistence.testing.framework.*;

-import org.eclipse.persistence.testing.tests.clientserver.*;

-import org.eclipse.persistence.tools.schemaframework.PopulationManager;

-

-public class QueryFrameworkTestSuite extends TestSuite {

+ ******************************************************************************/
+package org.eclipse.persistence.testing.tests.queries;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Vector;
+
+import org.eclipse.persistence.expressions.ExpressionBuilder;
+import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
+import org.eclipse.persistence.queries.ComplexQueryResult;
+import org.eclipse.persistence.queries.Cursor;
+import org.eclipse.persistence.queries.DatabaseQuery;
+import org.eclipse.persistence.queries.MethodBaseQueryRedirector;
+import org.eclipse.persistence.queries.ObjectLevelReadQuery;
+import org.eclipse.persistence.queries.QueryRedirector;
+import org.eclipse.persistence.queries.ReadAllQuery;
+import org.eclipse.persistence.queries.ReadObjectQuery;
+import org.eclipse.persistence.queries.ReportQuery;
+import org.eclipse.persistence.queries.ScrollableCursorPolicy;
+import org.eclipse.persistence.sessions.DatabaseRecord;
+import org.eclipse.persistence.sessions.Record;
+import org.eclipse.persistence.sessions.UnitOfWork;
+import org.eclipse.persistence.testing.framework.TestCase;
+import org.eclipse.persistence.testing.framework.TestSuite;
+import org.eclipse.persistence.testing.models.employee.domain.Employee;
+import org.eclipse.persistence.testing.models.employee.domain.EmployeePopulator;
+import org.eclipse.persistence.testing.models.employee.domain.LargeProject;
+import org.eclipse.persistence.testing.models.employee.domain.Project;
+import org.eclipse.persistence.testing.tests.clientserver.ServerSessionTestAdapter;
+import org.eclipse.persistence.tools.schemaframework.PopulationManager;
+
+public class QueryFrameworkTestSuite extends TestSuite {
     public QueryFrameworkTestSuite() {

         setDescription("This suite tests all of the functionality of the query framework.");

     }

@@ -34,18 +56,20 @@
         setDescription("This suite tests all of the functionality of the query framework.");

     }

 

-    public void addTests() {

-        addSRGTests();

-        addTest(new QueryTimeoutTest());

-        //Add new tests here, if any.

-        addTest(new ServerSessionTestAdapter(new PessimisticLockNoLockJoinedTest()));

-        addTest(new ReadAllNoDistinctTest());

-        addTest(new PartialAttributeTestWithJoinAttribute());

-        addTest(new PartialAttributeDistinctOrderByTest());

-        addTest(new FourPartialAttributeTestsWithJoinAttribute());

-        addTest(buildReadOnlyQueryTest());

-        addTest(buildGetSQLTest());

-        addTest(buildJoinSubclassesQueryTest());

+    public void addTests() {
+        addSRGTests();
+        addTest(new QueryTimeoutTest());
+        //Add new tests here, if any.
+        addTest(new ServerSessionTestAdapter(new PessimisticLockNoLockJoinedTest()));
+        addTest(new ReadAllNoDistinctTest());
+        addTest(new ObjectLevelReadQueryTest.CustomQueryRaceConditionsInReadAllQueryTest());
+        addTest(new ObjectLevelReadQueryTest.CustomQueryRaceConditionsInReadObjectQueryTest());
+        addTest(new PartialAttributeTestWithJoinAttribute());
+        addTest(new PartialAttributeDistinctOrderByTest());
+        addTest(new FourPartialAttributeTestsWithJoinAttribute());
+        addTest(buildReadOnlyQueryTest());
+        addTest(buildGetSQLTest());
+        addTest(buildJoinSubclassesQueryTest());
         addTest(buildRecordTest());

         // Bug 6450867 - TOPLINK-6069 ON READOBJECTQUERY ON ENTITY USING MULTIPLE TABLE MAPPING

         addTest(new ConformResultsWithMultitableAndJoiningTest());

diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadAllQuery.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadAllQuery.java
index 79ed566..733bb4e 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadAllQuery.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadAllQuery.java
@@ -204,32 +204,36 @@
      * Check to see if a custom query should be used for this query.

      * This is done before the query is copied and prepared/executed.

      * null means there is none.

-     */

-    @Override

-    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {

-        checkDescriptor(session);

-

-        // Check if user defined a custom query.

-        if (isCustomQueryUsed() == null) {

-            setIsCustomQueryUsed((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && isDefaultPropertiesQuery() && (!hasOrderByExpressions()) && (this.descriptor.getQueryManager().hasReadAllQuery()));

-        }

-        if (isCustomQueryUsed().booleanValue()) {

-            ReadAllQuery customQuery = this.descriptor.getQueryManager().getReadAllQuery();

-            if (this.accessors != null) {

-                customQuery = (ReadAllQuery) customQuery.clone();

-                customQuery.setIsExecutionClone(true);

-                customQuery.setAccessors(this.accessors);

-            }

-            return customQuery;

-        } else {

-            return null;

-        }

-    }

-

-    /**

-     * INTERNAL:

-     * Clone the query.

-     */

+     */
+    @Override
+    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
+        Boolean useCustomQuery = isCustomQueryUsed;
+
+        checkDescriptor(session);
+        // Check if user defined a custom query.
+        if (useCustomQuery == null) {
+            setIsCustomQueryUsed((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && isDefaultPropertiesQuery() && (!hasOrderByExpressions()) && (this.descriptor.getQueryManager().hasReadAllQuery()));
+            // Value of isCustomQueryUsed is updated by setIsCustomQueryUsed method.
+            useCustomQuery = isCustomQueryUsed;
+        }
+        if (useCustomQuery != null && useCustomQuery.booleanValue()) {
+            ReadAllQuery customQuery = this.descriptor.getQueryManager().getReadAllQuery();
+            if (this.accessors != null) {
+                customQuery = (ReadAllQuery) customQuery.clone();
+                customQuery.setIsExecutionClone(true);
+                customQuery.setAccessors(this.accessors);
+            }
+            isCustomQueryUsed = useCustomQuery;
+            return customQuery;
+        }
+        isCustomQueryUsed = useCustomQuery;
+        return null;
+    }
+
+    /**
+     * INTERNAL:
+     * Clone the query.
+     */
     @Override

     public Object clone() {

         ReadAllQuery cloneQuery = (ReadAllQuery)super.clone();

diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadObjectQuery.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadObjectQuery.java
index fed5d30..0abfb37 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadObjectQuery.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/queries/ReadObjectQuery.java
@@ -1,8 +1,8 @@
-/*******************************************************************************

- * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.

- * This program and the accompanying materials are made available under the 

- * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 

- * which accompanies this distribution. 

+/*******************************************************************************
+ * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html

  * and the Eclipse Distribution License is available at 

  * http://www.eclipse.org/org/documents/edl-v10.php.

@@ -30,13 +30,12 @@
 import org.eclipse.persistence.internal.sessions.AbstractSession;

 import org.eclipse.persistence.internal.sessions.ResultSetRecord;

 import org.eclipse.persistence.internal.sessions.SimpleResultSetRecord;

-import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;

-import org.eclipse.persistence.internal.helper.*;

-import org.eclipse.persistence.descriptors.ClassDescriptor;

-import org.eclipse.persistence.descriptors.DescriptorQueryManager;

-import org.eclipse.persistence.exceptions.*;

-import org.eclipse.persistence.expressions.*;

-import org.eclipse.persistence.sessions.DatabaseRecord;

+import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
+import org.eclipse.persistence.internal.helper.*;
+import org.eclipse.persistence.descriptors.ClassDescriptor;
+import org.eclipse.persistence.exceptions.*;
+import org.eclipse.persistence.expressions.*;
+import org.eclipse.persistence.sessions.DatabaseRecord;
 import org.eclipse.persistence.sessions.SessionProfiler;

 import org.eclipse.persistence.sessions.remote.*;

 import org.eclipse.persistence.tools.profiler.QueryMonitor;

@@ -295,62 +294,58 @@
      * INTERNAL:

      * Check to see if a custom query should be used for this query.

      * This is done before the query is copied and prepared/executed.

-     * null means there is none.

-     */

-    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {

-        if (this.descriptor == null) {

-            checkDescriptor(session);

-        }

-

-        if (this.isCustomQueryUsed == null) {

-            // Check if user defined a custom query in the query manager.

-            if (!this.isUserDefined) {

-                if (!isCallQuery()) {

-                    DescriptorQueryManager descriptorQueryManager = this.descriptor.getQueryManager();

-        

-                    // By default all descriptors have a custom ("static") read-object query.

-                    // This allows the read-object query and SQL to be prepare once.

-                    if (descriptorQueryManager.hasReadObjectQuery()) {

-                        // If the query require special SQL generation or execution do not use the static read object query.

-                        // PERF: the read-object query should always be static to ensure no regeneration of SQL.

-                        if ((!hasJoining() || !this.joinedAttributeManager.hasJoinedAttributeExpressions()) && (!hasPartialAttributeExpressions()) && (redirector == null) && !doNotRedirect && (!hasAsOfClause()) && (!hasNonDefaultFetchGroup())

-                                && (this.shouldUseSerializedObjectPolicy == shouldUseSerializedObjectPolicyDefault) 

-                                && this.wasDefaultLockMode && (shouldBindAllParameters == null) && (this.hintString == null)) {

-                            if ((this.selectionId != null) || (this.selectionObject != null)) {// Must be primary key.

-                                this.isCustomQueryUsed = true;

-                            } else {

-                                Expression selectionCriteria = getSelectionCriteria();

-                                if (selectionCriteria != null) {

-                                    AbstractRecord primaryKeyRow = this.descriptor.getObjectBuilder().extractPrimaryKeyRowFromExpression(selectionCriteria, translationRow, session);                

-                                    // Only execute the query if the selection criteria has the primary key fields set

-                                    if (primaryKeyRow != null) {

-                                        this.isCustomQueryUsed = true;

-                                    }

-                                }

-                            }

-                        }

-                    }

-                }

-            }

-            if (this.isCustomQueryUsed == null) {

-                this.isCustomQueryUsed = false;

-            }

-        }

-        

-        if (this.isCustomQueryUsed.booleanValue()) {

-            ReadObjectQuery customQuery = this.descriptor.getQueryManager().getReadObjectQuery();

-            if (this.accessors != null) {

-                customQuery = (ReadObjectQuery) customQuery.clone();

-                customQuery.setIsExecutionClone(true);

-                customQuery.setAccessors(this.accessors);

-            }

-            return customQuery;

-        } else {

-            return null;

-        }

-    }

-

-    /**

+     * null means there is none.
+     */
+    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
+        Boolean useCustomQuery = isCustomQueryUsed;
+
+        checkDescriptor(session);
+        if (useCustomQuery == null) {
+            // Check if user defined a custom query in the query manager.
+            if (!this.isUserDefined) {
+                if (!isCallQuery()
+                        // By default all descriptors have a custom ("static") read-object query.
+                        // This allows the read-object query and SQL to be prepare once.
+                        && this.descriptor.getQueryManager().hasReadObjectQuery()) {
+                    // If the query require special SQL generation or execution do not use the static read object query.
+                    // PERF: the read-object query should always be static to ensure no regeneration of SQL.
+                    if ((!hasJoining() || !this.joinedAttributeManager.hasJoinedAttributeExpressions()) && (!hasPartialAttributeExpressions())
+                            && (redirector == null) && !doNotRedirect && (!hasAsOfClause()) && (!hasNonDefaultFetchGroup())
+                            && (this.shouldUseSerializedObjectPolicy == shouldUseSerializedObjectPolicyDefault)
+                            && this.wasDefaultLockMode && (shouldBindAllParameters == null) && (this.hintString == null)) {
+                        if ((this.selectionId != null) || (this.selectionObject != null)) {// Must be primary key.
+                            useCustomQuery = Boolean.TRUE;
+                        } else {
+                            Expression selectionCriteria = getSelectionCriteria();
+                            if (selectionCriteria != null) {
+                                AbstractRecord primaryKeyRow = this.descriptor.getObjectBuilder().extractPrimaryKeyRowFromExpression(selectionCriteria, translationRow, session);
+                                // Only execute the query if the selection criteria has the primary key fields set
+                                if (primaryKeyRow != null) {
+                                    useCustomQuery = Boolean.TRUE;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        //#436871 - attempt to limit cases for race-condition
+        if (useCustomQuery != null && useCustomQuery.booleanValue()) {
+            ReadObjectQuery customQuery = this.descriptor.getQueryManager().getReadObjectQuery();
+            if (this.accessors != null) {
+                customQuery = (ReadObjectQuery) customQuery.clone();
+                customQuery.setIsExecutionClone(true);
+                customQuery.setAccessors(this.accessors);
+            }
+            isCustomQueryUsed = useCustomQuery;
+            return customQuery;
+        }
+        isCustomQueryUsed = useCustomQuery;
+        return null;
+    }
+
+    /**
      * INTERNAL:

      * Conform the result in the UnitOfWork.

      */