/*
 * 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.openjpa.persistence.lockmgr;

import java.sql.Connection;
import java.sql.ResultSet;

import jakarta.persistence.EntityManager;
import jakarta.persistence.LockModeType;
import jakarta.persistence.Query;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.kernel.EagerFetchModes;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfigurationImpl;
import org.apache.openjpa.jdbc.kernel.LRSSizes;
import org.apache.openjpa.jdbc.sql.JoinSyntaxes;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.LockLevels;
import org.apache.openjpa.kernel.MixedLockLevels;
import org.apache.openjpa.kernel.QueryFlushModes;
import org.apache.openjpa.persistence.EntityManagerImpl;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.jdbc.FetchDirection;
import org.apache.openjpa.persistence.jdbc.FetchMode;
import org.apache.openjpa.persistence.jdbc.IsolationLevel;
import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan;
import org.apache.openjpa.persistence.jdbc.JoinSyntax;
import org.apache.openjpa.persistence.jdbc.LRSSizeAlgorithm;
import org.apache.openjpa.persistence.jdbc.ResultSetType;

/**
 * Test FetchPlan and FetchConfiguration hints processing for use in
 * JPA 2.0 em.find/refresh/lock(... Map) and query.X(... Map) methods.
 *
 * Supported hints:
 *   jakarta.persistence.lock.timeout
 *   jakarta.persistence.query.timeout
 *   openjpa.ExtendedPathLookup (?)
 *   openjpa.FetchBatchSize
 *   openjpa.FetchPlan.EagerFetchMode
 *   openjpa.FetchPlan.ExtendedPathLookup (?)
 *   openjpa.FetchPlan.FetchBatchSize
 *   openjpa.FetchPlan.FetchDirection
 *   openjpa.FetchPlan.Isolation
 *   openjpa.FetchPlan.JoinSyntax
 *   openjpa.FetchPlan.LRSSize
 *   openjpa.FetchPlan.LRSSizeAlgorithm
 *   openjpa.FetchPlan.LockTimeout
 *   openjpa.FetchPlan.MaxFetchDepth
 *   openjpa.FetchPlan.QueryTimeout
 *   openjpa.FetchPlan.ReadLockMode
 *   openjpa.FetchPlan.ResultSetType
 *   openjpa.FetchPlan.SubclassFetchMode
 *   openjpa.FetchPlan.WriteLockMode
 *   openjpa.FlushBeforeQueries
 *   openjpa.LockTimeout
 *   openjpa.MaxFetchDepth
 *   openjpa.QueryTimeout
 *   openjpa.ReadLockLevel
 *   openjpa.WriteLockLevel
 *   openjpa.jdbc.EagerFetchMode
 *   openjpa.jdbc.FetchDirection
 *   openjpa.jdbc.JoinSyntax
 *   openjpa.jdbc.LRSSize
 *   openjpa.jdbc.ResultSetType
 *   openjpa.jdbc.SubclassFetchMode
 *   openjpa.jdbc.TransactionIsolation
 */
public class TestFetchHints extends SequencedActionsTest {
    @Override
    public void setUp() {
        setUp(LockEmployee.class
            , "openjpa.LockManager", "mixed"
            );
        commonSetUp();
    }

    /* Good
     * Test "openjpa.FetchBatchSize" hint
     */
    public void testFetchBatchSizeHint() {
        String hintName = "openjpa.FetchBatchSize";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager)em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        fetchBatchSizeHintTest(fPlan, fConfig, hintName, "-1", -1);
        fetchBatchSizeHintTest(fPlan, fConfig, hintName, -1, -1);
        fetchBatchSizeHintTest(fPlan, fConfig, hintName, "100", 100);
        fetchBatchSizeHintTest(fPlan, fConfig, hintName, 100, 100);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "xxxxx");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setFetchBatchSize(999);
            fPlan.setHint(hintName, FetchConfiguration.DEFAULT);
            assertEquals(fPlan.getFetchBatchSize(), -1);
        } catch (Exception e) {
            fail("Unexpected " + e.getClass().getName());
        }
        em.close();
    }

    /* Good
     * Test "openjpa.FetchPlan.FetchBatchSize" hint
     */
    public void testFetchPlanFetchBatchSizeHint() {
        String hintName = "openjpa.FetchPlan.FetchBatchSize";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager)em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        fetchBatchSizeHintTest(fPlan, fConfig, hintName, "0", 0);
        fetchBatchSizeHintTest(fPlan, fConfig, hintName, 0, 0);
        fetchBatchSizeHintTest(fPlan, fConfig, hintName, "500", 500);
        fetchBatchSizeHintTest(fPlan, fConfig, hintName, 500, 500);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setFetchBatchSize(999);
            fPlan.setHint(hintName, FetchConfiguration.DEFAULT);
            assertEquals(fPlan.getFetchBatchSize(), -1);
        } catch (Exception e) {
            fail("Unexpected " + e.getClass().getName());
        }
        em.close();
    }

    private void fetchBatchSizeHintTest(JDBCFetchPlan fPlan,
        JDBCFetchConfigurationImpl fConfig, String hint, Object value,
        int expected) {
        fConfig.setFetchBatchSize(999);
        fPlan.setHint(hint, value);
        Object getValue = fPlan.getHint(hint);
        assertEquals(value.getClass(), getValue.getClass());
        assertEquals(value, getValue);
        assertEquals(expected, fPlan.getFetchBatchSize());
        assertEquals(expected, fConfig.getFetchBatchSize());
    }

    /* Good
     * Test "openjpa.FetchPlan.EagerFetchMode" hint
     */
    public void testFetchPlanEagerFetchModeHint() {
        String hintName = "openjpa.FetchPlan.EagerFetchMode";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        eagerFetchModeHintTest(fPlan, fConfig, hintName, "none",
            FetchMode.NONE, EagerFetchModes.EAGER_NONE);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, FetchMode.NONE.name(),
            FetchMode.NONE, EagerFetchModes.EAGER_NONE);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, FetchMode.NONE,
            FetchMode.NONE, EagerFetchModes.EAGER_NONE);

        eagerFetchModeHintTest(fPlan, fConfig, hintName, "parallel",
            FetchMode.PARALLEL, EagerFetchModes.EAGER_PARALLEL);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, FetchMode.PARALLEL
            .name(), FetchMode.PARALLEL, EagerFetchModes.EAGER_PARALLEL);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, FetchMode.PARALLEL,
            FetchMode.PARALLEL, EagerFetchModes.EAGER_PARALLEL);

        eagerFetchModeHintTest(fPlan, fConfig, hintName, "join",
            FetchMode.JOIN, EagerFetchModes.EAGER_JOIN);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, FetchMode.JOIN.name(),
            FetchMode.JOIN, EagerFetchModes.EAGER_JOIN);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, FetchMode.JOIN,
            FetchMode.JOIN, EagerFetchModes.EAGER_JOIN);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, -2);
            fPlan.setHint(hintName, -3);
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        em.close();
    }

    /* Good
     * Test "openjpa.jdbc.EagerFetchMode" hint
     */
    public void testJdbcEagerFetchModeHint() {
        String hintName = "openjpa.jdbc.EagerFetchMode";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        eagerFetchModeHintTest(fPlan, fConfig, hintName, String
            .valueOf(EagerFetchModes.EAGER_NONE), FetchMode.NONE,
            EagerFetchModes.EAGER_NONE);
        eagerFetchModeHintTest(fPlan, fConfig, hintName,
            EagerFetchModes.EAGER_NONE, FetchMode.NONE,
            EagerFetchModes.EAGER_NONE);

        eagerFetchModeHintTest(fPlan, fConfig, hintName,
            EagerFetchModes.EAGER_PARALLEL, FetchMode.PARALLEL,
            EagerFetchModes.EAGER_PARALLEL);
        eagerFetchModeHintTest(fPlan, fConfig, hintName, String
            .valueOf(EagerFetchModes.EAGER_PARALLEL), FetchMode.PARALLEL,
            EagerFetchModes.EAGER_PARALLEL);

        eagerFetchModeHintTest(fPlan, fConfig, hintName, String
            .valueOf(EagerFetchModes.EAGER_JOIN), FetchMode.JOIN,
            EagerFetchModes.EAGER_JOIN);
        eagerFetchModeHintTest(fPlan, fConfig, hintName,
            EagerFetchModes.EAGER_JOIN, FetchMode.JOIN,
            EagerFetchModes.EAGER_JOIN);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, -1);
            fPlan.setHint(hintName, -2);
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, FetchConfiguration.DEFAULT);
            assertEquals(fPlan.getEagerFetchMode(), FetchMode.PARALLEL);
        } catch (Exception e) {
              fail("Unexpected " + e.getClass().getName());
        }

        em.close();
    }

    private void eagerFetchModeHintTest(JDBCFetchPlan fPlan,
        JDBCFetchConfigurationImpl fConfig, String hint, Object value,
        Object expectedValue, int expected) {
        fPlan.setHint(hint, value);
        Object getValue = fPlan.getHint(hint);
        assertEquals(value.getClass(), getValue.getClass());
        assertEquals(value, getValue);
        assertEquals(expectedValue, fPlan.getEagerFetchMode());
        assertEquals(expected, fConfig.getEagerFetchMode());
    }

    /* Good
     * Test "openjpa.FetchPlan.JoinSyntax" hint
     */
    public void testFetchPlanJoinSyntaxHint() {
        String hintName = "openjpa.FetchPlan.JoinSyntax";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager)em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        joinSyntaxHintTest(fPlan, fConfig, hintName, "sql92",
            JoinSyntax.SQL92, JoinSyntaxes.SYNTAX_SQL92);
        joinSyntaxHintTest(fPlan, fConfig, hintName, JoinSyntax.SQL92.name(),
            JoinSyntax.SQL92, JoinSyntaxes.SYNTAX_SQL92);
        joinSyntaxHintTest(fPlan, fConfig, hintName, JoinSyntax.SQL92,
            JoinSyntax.SQL92, JoinSyntaxes.SYNTAX_SQL92);

        joinSyntaxHintTest(fPlan, fConfig, hintName, "traditional",
            JoinSyntax.TRADITIONAL, JoinSyntaxes.SYNTAX_TRADITIONAL);
        joinSyntaxHintTest(fPlan, fConfig, hintName, JoinSyntax.TRADITIONAL
            .name(), JoinSyntax.TRADITIONAL, JoinSyntaxes.SYNTAX_TRADITIONAL);
        joinSyntaxHintTest(fPlan, fConfig, hintName, JoinSyntax.TRADITIONAL,
            JoinSyntax.TRADITIONAL, JoinSyntaxes.SYNTAX_TRADITIONAL);

        joinSyntaxHintTest(fPlan, fConfig, hintName, "database",
            JoinSyntax.DATABASE, JoinSyntaxes.SYNTAX_DATABASE);
        joinSyntaxHintTest(fPlan, fConfig, hintName,
            JoinSyntax.DATABASE.name(), JoinSyntax.DATABASE,
            JoinSyntaxes.SYNTAX_DATABASE);
        joinSyntaxHintTest(fPlan, fConfig, hintName, JoinSyntax.DATABASE,
            JoinSyntax.DATABASE, JoinSyntaxes.SYNTAX_DATABASE);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, -1);
            fPlan.setHint(hintName, -2);
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        em.close();
    }

    /* Good
     * Test "openjpa.jdbc.JoinSyntax" hint
     */
    public void testJdbcJoinSyntaxHint() {
        String hintName = "openjpa.jdbc.JoinSyntax";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        joinSyntaxHintTest(fPlan, fConfig, hintName, String
            .valueOf(JoinSyntaxes.SYNTAX_SQL92), JoinSyntax.SQL92,
            JoinSyntaxes.SYNTAX_SQL92);
        joinSyntaxHintTest(fPlan, fConfig, hintName, JoinSyntaxes.SYNTAX_SQL92,
            JoinSyntax.SQL92, JoinSyntaxes.SYNTAX_SQL92);

        joinSyntaxHintTest(fPlan, fConfig, hintName, String
            .valueOf(JoinSyntaxes.SYNTAX_TRADITIONAL), JoinSyntax.TRADITIONAL,
            JoinSyntaxes.SYNTAX_TRADITIONAL);
        joinSyntaxHintTest(fPlan, fConfig, hintName,
            JoinSyntaxes.SYNTAX_TRADITIONAL, JoinSyntax.TRADITIONAL,
            JoinSyntaxes.SYNTAX_TRADITIONAL);

        joinSyntaxHintTest(fPlan, fConfig, hintName, String
            .valueOf(JoinSyntaxes.SYNTAX_DATABASE), JoinSyntax.DATABASE,
            JoinSyntaxes.SYNTAX_DATABASE);
        joinSyntaxHintTest(fPlan, fConfig, hintName,
            JoinSyntaxes.SYNTAX_DATABASE, JoinSyntax.DATABASE,
            JoinSyntaxes.SYNTAX_DATABASE);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, -1);
            fPlan.setHint(hintName, -2);
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, FetchConfiguration.DEFAULT);
            assertEquals(fConfig.getJoinSyntax(),
                ((JDBCConfiguration) fConfig.getContext().getConfiguration())
                    .getDBDictionaryInstance().joinSyntax);
        } catch (Exception e) {
            fail("Unexpected " + e.getClass().getName());
        }
        em.close();
    }

    private void joinSyntaxHintTest(JDBCFetchPlan fPlan,
        JDBCFetchConfigurationImpl fConfig, String hint, Object value,
        Object expectedValue, int expected) {
        fPlan.setHint(hint, value);
        Object getValue = fPlan.getHint(hint);
        assertEquals(value.getClass(), getValue.getClass());
        assertEquals(value, getValue);
        assertEquals(expectedValue, fPlan.getJoinSyntax());
        assertEquals(expected, fConfig.getJoinSyntax());
    }

    /* Good
     * Test "openjpa.FetchPlan.FetchDirection" hint
     */
    public void testFetchPlanFetchDirectionHint() {
        String hintName = "openjpa.FetchPlan.FetchDirection";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        fetchDirectionHintTest(fPlan, fConfig, hintName, "forward",
            FetchDirection.FORWARD, ResultSet.FETCH_FORWARD);
        fetchDirectionHintTest(fPlan, fConfig, hintName, FetchDirection.FORWARD
            .name(), FetchDirection.FORWARD, ResultSet.FETCH_FORWARD);
        fetchDirectionHintTest(fPlan, fConfig, hintName,
            FetchDirection.FORWARD, FetchDirection.FORWARD,
            ResultSet.FETCH_FORWARD);

        fetchDirectionHintTest(fPlan, fConfig, hintName, "reverse",
            FetchDirection.REVERSE, ResultSet.FETCH_REVERSE);
        fetchDirectionHintTest(fPlan, fConfig, hintName, FetchDirection.REVERSE
            .name(), FetchDirection.REVERSE, ResultSet.FETCH_REVERSE);
        fetchDirectionHintTest(fPlan, fConfig, hintName,
            FetchDirection.REVERSE, FetchDirection.REVERSE,
            ResultSet.FETCH_REVERSE);

        fetchDirectionHintTest(fPlan, fConfig, hintName, "unknown",
            FetchDirection.UNKNOWN, ResultSet.FETCH_UNKNOWN);
        fetchDirectionHintTest(fPlan, fConfig, hintName, FetchDirection.UNKNOWN
            .name(), FetchDirection.UNKNOWN, ResultSet.FETCH_UNKNOWN);
        fetchDirectionHintTest(fPlan, fConfig, hintName,
            FetchDirection.UNKNOWN, FetchDirection.UNKNOWN,
            ResultSet.FETCH_UNKNOWN);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, -1);
            fPlan.setHint(hintName, -2);
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        em.close();
    }

    /* Good
     * Test "openjpa.jdbc.FetchDirection" hint
     */
    public void testJdbcFetchDirectionHint() {
        String hintName = "openjpa.jdbc.FetchDirection";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        fetchDirectionHintTest(fPlan, fConfig, hintName, String
            .valueOf(ResultSet.FETCH_FORWARD), FetchDirection.FORWARD,
            ResultSet.FETCH_FORWARD);
        fetchDirectionHintTest(fPlan, fConfig, hintName,
            ResultSet.FETCH_FORWARD, FetchDirection.FORWARD,
            ResultSet.FETCH_FORWARD);

        fetchDirectionHintTest(fPlan, fConfig, hintName, String
            .valueOf(ResultSet.FETCH_REVERSE), FetchDirection.REVERSE,
            ResultSet.FETCH_REVERSE);
        fetchDirectionHintTest(fPlan, fConfig, hintName,
            ResultSet.FETCH_REVERSE, FetchDirection.REVERSE,
            ResultSet.FETCH_REVERSE);

        fetchDirectionHintTest(fPlan, fConfig, hintName, String
            .valueOf(ResultSet.FETCH_UNKNOWN), FetchDirection.UNKNOWN,
            ResultSet.FETCH_UNKNOWN);
        fetchDirectionHintTest(fPlan, fConfig, hintName,
            ResultSet.FETCH_UNKNOWN, FetchDirection.UNKNOWN,
            ResultSet.FETCH_UNKNOWN);

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, -1);
            fPlan.setHint(hintName, -2);
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, FetchConfiguration.DEFAULT);
            assertEquals(fConfig.getFetchDirection(), ResultSet.FETCH_FORWARD);
        } catch (Exception e) {
            fail("Unexpected " + e.getClass().getName());
        }
        em.close();
    }

    private void fetchDirectionHintTest(JDBCFetchPlan fPlan,
        JDBCFetchConfigurationImpl fConfig, String hint, Object value,
        Object expectedValue, int expected) {
        fPlan.setHint(hint, value);
        Object getValue = fPlan.getHint(hint);
        assertEquals(value.getClass(), getValue.getClass());
        assertEquals(value, getValue);
        assertEquals(expectedValue, fPlan.getFetchDirection());
        assertEquals(expected, fConfig.getFetchDirection());
    }

    /* Good
     * Test "openjpa.FetchPlan.Isolation" hint
     */
    public void testFetchPlanIsolationHint() {
        String hintName = "openjpa.FetchPlan.Isolation";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        isolationHintTest(oem, fPlan, fConfig, hintName, "default",
            IsolationLevel.DEFAULT, -1);
        isolationHintTest(oem, fPlan, fConfig, hintName, "DEFAULT",
            IsolationLevel.DEFAULT, -1);
        isolationHintTest(oem, fPlan, fConfig, hintName,
            IsolationLevel.DEFAULT, IsolationLevel.DEFAULT, -1);

        boolean supportIsolationForUpdate = ((JDBCConfiguration) fConfig
            .getContext().getConfiguration()).getDBDictionaryInstance()
            .supportsIsolationForUpdate();
        if (supportIsolationForUpdate) {
            isolationHintTest(oem, fPlan, fConfig, hintName, "none",
                IsolationLevel.NONE, Connection.TRANSACTION_NONE);
            isolationHintTest(oem, fPlan, fConfig, hintName, "NONE",
                IsolationLevel.NONE, Connection.TRANSACTION_NONE);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                IsolationLevel.NONE, IsolationLevel.NONE,
                Connection.TRANSACTION_NONE);

            isolationHintTest(oem, fPlan, fConfig, hintName,
                "read-uncommitted", IsolationLevel.READ_UNCOMMITTED,
                Connection.TRANSACTION_READ_UNCOMMITTED);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                "READ_UNCOMMITTED", IsolationLevel.READ_UNCOMMITTED,
                Connection.TRANSACTION_READ_UNCOMMITTED);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                IsolationLevel.READ_UNCOMMITTED,
                IsolationLevel.READ_UNCOMMITTED,
                Connection.TRANSACTION_READ_UNCOMMITTED);

            isolationHintTest(oem, fPlan, fConfig, hintName, "read-committed",
                IsolationLevel.READ_COMMITTED,
                Connection.TRANSACTION_READ_COMMITTED);
            isolationHintTest(oem, fPlan, fConfig, hintName, "READ_COMMITTED",
                IsolationLevel.READ_COMMITTED,
                Connection.TRANSACTION_READ_COMMITTED);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                IsolationLevel.READ_COMMITTED, IsolationLevel.READ_COMMITTED,
                Connection.TRANSACTION_READ_COMMITTED);

            isolationHintTest(oem, fPlan, fConfig, hintName, "repeatable-read",
                IsolationLevel.REPEATABLE_READ,
                Connection.TRANSACTION_REPEATABLE_READ);
            isolationHintTest(oem, fPlan, fConfig, hintName, "REPEATABLE_READ",
                IsolationLevel.REPEATABLE_READ,
                Connection.TRANSACTION_REPEATABLE_READ);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                IsolationLevel.REPEATABLE_READ, IsolationLevel.REPEATABLE_READ,
                Connection.TRANSACTION_REPEATABLE_READ);

            isolationHintTest(oem, fPlan, fConfig, hintName, "serializable",
                IsolationLevel.SERIALIZABLE,
                Connection.TRANSACTION_SERIALIZABLE);
            isolationHintTest(oem, fPlan, fConfig, hintName, "SERIALIZABLE",
                IsolationLevel.SERIALIZABLE,
                Connection.TRANSACTION_SERIALIZABLE);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                IsolationLevel.SERIALIZABLE, IsolationLevel.SERIALIZABLE,
                Connection.TRANSACTION_SERIALIZABLE);
        }

        try {
            fPlan.setHint(hintName, "xxxxx");
            fPlan.setHint(hintName, "yyyyy");
            fail("Expecting a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        try {
            fPlan.setHint(hintName, "12345");
            fPlan.setHint(hintName, "67890");
            fail("Expecting a a IllegalArgumentException.");
        } catch (Exception e) {
            assertTrue("Caught expected exception",
                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
        }
        // Is not -1 is valid value for transaction isolation level?
//        try {
//            fPlan.setHint(hintName, -1);
//            fPlan.setHint(hintName, -1);
//            fail("Expecting a a IllegalArgumentException.");
//        } catch (Exception e) {
//            assertTrue("Caught expected exception",
//                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
//        }
        em.close();
    }

    /* Good
     * Test "openjpa.jdbc.TransactionIsolation" hint
     */
    public void testJdbcTransactionIsolationHint() {
        String hintName = "openjpa.jdbc.TransactionIsolation";

        EntityManager em = emf.createEntityManager();
        OpenJPAEntityManager oem = (OpenJPAEntityManager) em.getDelegate();
        JDBCFetchPlan fPlan = (JDBCFetchPlan) oem.getFetchPlan();
        JDBCFetchConfigurationImpl fConfig = (JDBCFetchConfigurationImpl)
            ((EntityManagerImpl) oem).getBroker().getFetchConfiguration();

        isolationHintTest(oem, fPlan, fConfig, hintName, "-1",
            IsolationLevel.DEFAULT, -1);
        isolationHintTest(oem, fPlan, fConfig, hintName, -1,
            IsolationLevel.DEFAULT, -1);

        boolean supportIsolationForUpdate = ((JDBCConfiguration) fConfig
            .getContext().getConfiguration()).getDBDictionaryInstance()
            .supportsIsolationForUpdate();
        if (supportIsolationForUpdate) {
            isolationHintTest(oem, fPlan, fConfig, hintName, String
                .valueOf(Connection.TRANSACTION_NONE), IsolationLevel.NONE,
                Connection.TRANSACTION_NONE);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                Connection.TRANSACTION_NONE, IsolationLevel.NONE,
                Connection.TRANSACTION_NONE);

            isolationHintTest(oem, fPlan, fConfig, hintName, String
                .valueOf(Connection.TRANSACTION_READ_UNCOMMITTED),
                IsolationLevel.READ_UNCOMMITTED,
                Connection.TRANSACTION_READ_UNCOMMITTED);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                Connection.TRANSACTION_READ_UNCOMMITTED,
                IsolationLevel.READ_UNCOMMITTED,
                Connection.TRANSACTION_READ_UNCOMMITTED);

            isolationHintTest(oem, fPlan, fConfig, hintName, String
                .valueOf(Connection.TRANSACTION_READ_COMMITTED),
                IsolationLevel.READ_COMMITTED,
                Connection.TRANSACTION_READ_COMMITTED);
            isolationHintTest(oem, fPlan, fConfig, hintName,
                Connection.TRANSACTION_READ_COMMITTED,
                IsolationLevel.READ_COMMITTED,
                Connection.TRANSACTION_READ_COMMITTED);

            isolationHintTest(oem, fPlan, fConfig, hintName, String
                .valueOf(Connection.TRANSACTION_REPEATABLE_READ),
                IsolationLevel.REPEATABLE_READ,
