/* * 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.query; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.Query; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.jdbc.conf.JDBCConfiguration; import org.apache.openjpa.jdbc.sql.DB2Dictionary; import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.jdbc.sql.DerbyDictionary; import org.apache.openjpa.jdbc.sql.InformixDictionary; import org.apache.openjpa.jdbc.sql.OracleDictionary; import org.apache.openjpa.jdbc.sql.SQLServerDictionary; import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.persistence.JPAFacadeHelper; import org.apache.openjpa.persistence.OpenJPAEntityManager; import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory; import org.apache.openjpa.persistence.OpenJPAPersistence; import org.apache.openjpa.persistence.OpenJPAQuery; import org.apache.openjpa.persistence.OptimisticLockException; import org.apache.openjpa.persistence.PersistenceException; import org.apache.openjpa.persistence.QueryTimeoutException; import org.apache.openjpa.persistence.query.common.apps.QTimeout; import org.apache.openjpa.persistence.test.SQLListenerTestCase; /** * Tests the new query timeout hint support in the JPA 2.0 spec. * Query timeout scenarios being tested: * 1) By default, there is no timeout * 2) Setting timeout to 0 is same as no timeout (JDBC defined) * 2.1) using Map properties on createEMF (or PU properties) * 2.2) using the QueryHint annotation * 2.3) using setHint() * 3) Setting timeout to msecs < DELAY value causes new * javax.persistence.QueryTimeoutException for databases that do not * cause a rollback or a PersistenceException if they do, when set by: * 3.1) using persistence.xml PU properties (or createEMF Map properties) * 3.2) using the QueryHint annotation * 3.3) calling setHint() * Query operations to validate through cross coverage of items #1-#3: * a) getResultList() * b) getSingleResult() * c) executeUpdate() - Requires the db UPDATE trigger * Other behaviors to test for: * 4) Setting timeout to -1 should be treated as no timeout supplied * 5) Setting timeout to < -1 should throw an IllegalArgumentExpection * 6) Updates after EM.find()/findAll() are not affected by query timeout * Exception generation to test, covered by 31c and 33c: * If the DB query timeout does not cause a transaction rollback, then a * QueryTimeoutException should be thrown. * Else if the DB query timeout causes a transaction rollback, then a * PersistenceException should be thrown instead of a QTE. * * @version $Rev$ $Date$ */ public class TestQueryTimeout extends SQLListenerTestCase { private DBDictionary dict = null; // skip test due to unsupported/untested db private boolean skipTests = false; // skip some tests due to no DB triggers to cause delay timeout exceptions private boolean skipExceptionTests = false; // skip some tests due to no SELECT query timeouts being generated by the DB private boolean noSelectTimeouts = false; // default native query string to use for update testing private String nativeUpdateStr = new String("UPDATE QTimeout SET " + "stringField = ? WHERE mod(DELAY(2,id),2)=0"); @Override public void setUp() { super.setUp(CLEAR_TABLES, QTimeout.class); getLog().trace("setUp()"); dict = ((JDBCConfiguration) emf.getConfiguration()) .getDBDictionaryInstance(); assertNotNull(dict); // determine if we should run our tests on this DB platform and what // exception type to catch if (dict.supportsQueryTimeout) { if (dict instanceof DerbyDictionary) { // create some initial entities setupCreateEntities(); // create delay function only on Derby, other DBs manual setup setupCreateDBFunction(); // create triggers on all DBs // If this fails, skip running the timeout tests on this DB skipExceptionTests = !setupCreateDBTriggers(); } else if (dict instanceof DB2Dictionary) { setupCreateEntities(); // basic timeout tests fail to pop w/o multiple connections skipTests = true; skipExceptionTests = true; } else if ((dict instanceof SQLServerDictionary) || (dict instanceof OracleDictionary)) { // create some initial entities setupCreateEntities(); // skip running the SELECT timeout tests due to: // C# Delay.dll code on SQLServer // Embedded Java code on Oracle skipTests = true; noSelectTimeouts = true; // create triggers on all DBs skipExceptionTests = !setupCreateDBTriggers(); // special case for MS SQL Server and Oracle, as our // Function code doesn't support returning the id for now nativeUpdateStr = "UPDATE QTimeout SET stringField = ? " + "WHERE id = 1"; } else if (dict instanceof InformixDictionary) { // create some initial entities setupCreateEntities(); // create triggers on all DBs skipExceptionTests = !setupCreateDBTriggers(); } else { // unknown db, so skip all timeout tests skipTests = skipExceptionTests = noSelectTimeouts = true; setTestsDisabled(true); getLog().info("TestQueryTimeout tests are being skipped, due " + "to " + dict.platform + " not supporting Query Timeouts."); } } else { getLog().info("TestQueryTimeout tests are being skipped, " + "due to " + dict.platform + " not supporting Query Timeouts."); skipTests = skipExceptionTests = true; setTestsDisabled(true); } } /** * Scenario being tested: 1a) By default, there is no timeout for queries. * Expected Results: The DELAY function is being called and the query takes * 6000+ msecs to complete. */ public void testQueryTimeout1a() { if (skipTests) { getLog().trace("testQueryTimeout1a - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout1a() - No Query timeout"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createNamedQuery("NoHintList"); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); try { long startTime = System.currentTimeMillis(); @SuppressWarnings("unchecked") List results = q.getResultList(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout1a() - NoHintList runTime" + " msecs=" + runTime); // Hack - Windows sometimes returns 5999 instead of 6000+ assertTrue("Should have taken 6+ secs, but was msecs=" + runTime, runTime > 5900); assertEquals("Verify we found 2 results.", 2, results.size()); } catch (Exception e) { fail("Unexpected testQueryTimeout1a() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 1c) By default, there is no timeout for updates. * Expected Results: The DELAY function is being called and the query takes * 2000+ msecs to complete. */ public void testQueryTimeout1c() { if (skipTests) { getLog().trace("testQueryTimeout1c - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout1c() - No executeUpdate timeout"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createQuery("UPDATE QTimeout q SET q.stringField = " + ":strVal WHERE q.id = 1"); q.setParameter("strVal", new String("updated")); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); try { long startTime = System.currentTimeMillis(); em.getTransaction().begin(); int count = q.executeUpdate(); em.getTransaction().commit(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout1c() - executeUpdate runTime " + "msecs=" + runTime); assertTrue("Verify we received one result.", (count == 1)); // Hack - Windows sometimes returns 1999 instead of 2000+ assertTrue("Should have taken 2+ secs, but was msecs=" + runTime, runTime > 1900); } catch (Exception e) { fail("Unexpected testQueryTimeout1c() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 2.1.b) Explicit Map of properties to createEMF * with timeout=0 is treated the same as the default no query timeout. * Expected Results: The DELAY function is being called and the query * takes 2000+ msecs to complete. */ public void testQueryTimeout21b() { if (skipTests) { getLog().trace("testQueryTimeout21b - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout21b() - Map(timeout=0)"); OpenJPAEntityManagerFactory emf = null; OpenJPAEntityManager em = null; Integer setTime = new Integer(0); // create the Map to test overrides Map<String,String >props = new HashMap<String,String>(); props.put("javax.persistence.query.timeout", "0"); try { // create our EMF with our timeout property emf = OpenJPAPersistence.createEntityManagerFactory( "qtimeout-no-properties", "persistence3.xml", props); assertNotNull(emf); // verify Map properties updated the config OpenJPAConfiguration conf = emf.getConfiguration(); assertNotNull(conf); assertEquals("Map provided query timeout", setTime.intValue(), conf.getQueryTimeout()); // verify no default javax.persistence.query.timeout is supplied // as the Map properties are not passed through as hints em = emf.createEntityManager(); assertNotNull(em); OpenJPAQuery q = em.createNamedQuery("NoHintSingle"); Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // verify internal config values were updated assertEquals("Map provided query timeout", setTime.intValue(), q.getFetchPlan().getQueryTimeout()); try { long startTime = System.currentTimeMillis(); Object result = q.getSingleResult(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout21b() - NoHintSingle runTime " + "msecs=" + runTime); // Hack - Windows sometimes returns 1999 instead of 2000+ assertTrue("Should have taken 2+ secs, but was msecs=" + runTime, runTime > 1900); assertNotNull("Verify we received a result.", result); } catch (Exception e) { fail("Unexpected testQueryTimeout21b() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 2.2.a) Explicit annotated QueryHint of timeout=0 * is treated the same as the default no timeout for queries. * Expected Results: The DELAY function is being called and the query * takes 6000+ msecs to complete. */ public void testQueryTimeout22a() { if (skipTests) { getLog().trace("testQueryTimeout22a - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout22a() - QueryHint=0"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createNamedQuery("Hint0msec"); // verify javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = new Integer( (String) hints.get("javax.persistence.query.timeout")); getLog().trace("testQueryTimeout22a() - Retrieved hint " + "javax.persistence.query.timeout=" + timeout); assertEquals(timeout, new Integer(0)); try { long startTime = System.currentTimeMillis(); @SuppressWarnings("unchecked") List results = q.getResultList(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout22a() - Hint0msec runTime " + "msecs=" + runTime); // Hack - Windows sometimes returns 5999 instead of 6000+ assertTrue("Should have taken 6+ secs, but was msecs=" + runTime, runTime > 5900); assertEquals("Verify we found 2 results.", 2, results.size()); } catch (Exception e) { fail("Unexpected testQueryTimeout22a() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 2.3.b) Explicit setHint of timeout=0 is treated * the same as the default no timeout for queries. * Expected Results: The DELAY function is being called and the query * takes 2000+ msecs to complete. */ public void testQueryTimeout23b() { if (skipTests) { getLog().trace("testQueryTimeout23b - test is " + "being skipped for " + dict.platform); return; } Integer setTime = new Integer(0); getLog().trace("testQueryTimeout23b() - setHint(" + setTime + ")"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createNamedQuery("NoHintSingle"); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the timeout value to 0 and verify it was set getLog().trace("testQueryTimeout23b() - Setting hint " + "javax.persistence.query.timeout=" + setTime); q.setHint("javax.persistence.query.timeout", setTime); hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = (Integer) hints.get( "javax.persistence.query.timeout"); getLog().trace("testQueryTimeout23b() - Retrieved hint " + "javax.persistence.query.timeout=" + timeout); assertEquals(timeout, setTime); try { long startTime = System.currentTimeMillis(); Object result = q.getSingleResult(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout23b() - NoHintSingle runTime " + "msecs=" + runTime); // Hack - Windows sometimes returns 1999 instead of 2000+ assertTrue("Should have taken 2+ secs, but was msecs=" + runTime, runTime > 1900); assertNotNull("Verify we received a result.", result); } catch (Exception e) { fail("Unexpected testQueryTimeout23b() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 3.1.c) Explicit persistence.xml provided PU * property of timeout=1000 msecs will cause the query to timeout. * Expected Results: The DELAY function is being called and the query * takes 2000+ msecs to complete. */ public void testQueryTimeout31c() { if (skipExceptionTests) { getLog().trace("testQueryTimeout31c - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout31c() - PU(timeout=1000), " + "executeUpdate timeout"); OpenJPAEntityManagerFactory emf = null; OpenJPAEntityManager em = null; Integer setTime = new Integer(1000); boolean bRetry = true; try { // create our EMF with our PU set timeout property emf = OpenJPAPersistence.createEntityManagerFactory( "qtimeout-1000msecs", "persistence3.xml"); assertNotNull(emf); // verify PU properties updated the config OpenJPAConfiguration conf = emf.getConfiguration(); assertNotNull(conf); assertEquals("PU provided query timeout", setTime.intValue(), conf.getQueryTimeout()); // create EM and Query em = emf.createEntityManager(); assertNotNull(em); OpenJPAQuery q = em.createNativeQuery(nativeUpdateStr); q.setParameter(1, new String("updated")); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // verify internal config values were updated assertEquals("PU provided query timeout", setTime.intValue(), q.getFetchPlan().getQueryTimeout()); // verify queryTimeout on EM find operations em.getTransaction().begin(); // if we get a QTE, then retry this once to prove no db rollback for (int i=0; i<=1 && bRetry; i++) { try { long startTime = System.currentTimeMillis(); @SuppressWarnings("unused") int count = q.executeUpdate(); // exception should occur before commit em.getTransaction().commit(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout31c() - executeUpdate " + "runTime msecs=" + runTime); fail("QueryTimeout for executeUpdate failed to cause an " + "Exception in testQueryTimeout31c(" + setTime + " mscs), runTime msecs=" + runTime); } catch (Exception e) { // expected - Should cause a QueryTimeoutException for: // Derby, MS SQL // should only retry the statement if db did not rollback // which should be a QueryTimeoutException bRetry = checkException("testQueryTimeout31c()", e); // we're only retrying to prove the right exception type // was thrown and the transaction wasn't rolled back if (bRetry) getLog().trace("testQueryTimeout31c() - retrying... "); } } } finally { if ((em != null) && em.isOpen()) { if (em.getTransaction().isActive()) em.getTransaction().rollback(); em.close(); } } } /** * Scenario being tested: 3.2.a) Explicit annotated QueryHint of * timeout=1000 msecs will override the PU and Map provided timeouts * and cause the query to timeout. * Expected Results: QueryTimeoutException or PersistenceException */ public void testQueryTimeout32a() { if (skipExceptionTests || noSelectTimeouts) { getLog().trace("testQueryTimeout32a - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout32a() - PU(1000), Map(0), " + "QueryHint(1000)"); OpenJPAEntityManagerFactory emf = null; OpenJPAEntityManager em = null; Integer setTime = new Integer(0); // create the Map to test overrides Map<String,String> props = new HashMap<String,String>(); props.put("javax.persistence.query.timeout", "0"); try { // create our EMF with our PU set timeout property emf = OpenJPAPersistence.createEntityManagerFactory( "qtimeout-1000msecs", "persistence3.xml", props); assertNotNull(emf); // verify Map properties overrode the PU properties in config OpenJPAConfiguration conf = emf.getConfiguration(); assertNotNull(conf); assertEquals("Map provided query timeout", setTime.intValue(), conf.getQueryTimeout()); // create EM and named query em = emf.createEntityManager(); assertNotNull(em); OpenJPAQuery q = em.createNamedQuery("Hint1000msec"); setTime = 1000; // verify javax.persistence.query.timeout hint via annotation set Map<String, Object> hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = new Integer((String) hints.get( "javax.persistence.query.timeout")); getLog().trace( "testQueryTimeout32a() - Found javax.persistence.query.timeout=" + timeout); assertTrue("Expected to find a javax.persistence.query.timeout=" + setTime, (timeout.intValue() == setTime.intValue())); // verify internal config values were updated assertEquals("QueryHint provided query timeout", setTime.intValue(), q.getFetchPlan().getQueryTimeout()); try { long startTime = System.currentTimeMillis(); @SuppressWarnings( { "unchecked", "unused" }) List results = q.getResultList(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace( "testQueryTimeout32a() - Hint1000msec runTime msecs=" + runTime); //assertEquals("Should never get valid results due to the " + // "timeout.", 2, results.size()); fail("QueryTimeout annotation failed to cause an Exception " + "in testQueryTimeout32a(" + setTime + " msecs), runTime msecs=" + runTime); } catch (Exception e) { // expected checkException("testQueryTimeout32a()", e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 3.3.b) Explicit setHint of timeout to 1000 msecs * will cause the query to timeout. * Expected Results: QueryTimeoutException or PersistenceException */ public void testQueryTimeout33b() { if (skipExceptionTests || noSelectTimeouts) { getLog().trace("testQueryTimeout33b - test is " + "being skipped for " + dict.platform); return; } Integer setTime = new Integer(1000); getLog().trace("testQueryTimeout33b() - setHint(" + setTime + ")"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createNamedQuery("NoHintSingle"); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the timeout value and verify it was set getLog().trace("testQueryTimeout33b() - Setting hint " + "javax.persistence.query.timeout=" + setTime); q.setHint("javax.persistence.query.timeout", setTime); hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = (Integer) hints.get( "javax.persistence.query.timeout"); assertEquals(timeout, setTime); try { long startTime = System.currentTimeMillis(); @SuppressWarnings("unused") Object result = q.getSingleResult(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace( "testQueryTimeout33b() - NoHintSingle runTime msecs=" + runTime); //assertNull("Should never get valid result due to the timeout." // , result); fail("QueryTimeout annotation failed to cause an Exception " + "in testQueryTimeout33b(" + setTime + " mscs), runTime msecs=" + runTime); } catch (Exception e) { // expected checkException("testQueryTimeout33b()", e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 3.3.c) Explicit setHint of timeout to 1000 msecs * will cause the PU provided timeout=0 value to be overridden and the * executeUpdate to timeout. * Expected Results: QueryTimeoutException (Derby) or PersistenceException */ public void testQueryTimeout33c() { if (skipExceptionTests) { getLog().trace("testQueryTimeout33c - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout33c() - PU(timeout=0), setHint(1000)," + " executeUpdate timeout"); OpenJPAEntityManagerFactory emf = null; OpenJPAEntityManager em = null; Integer setTime = new Integer(0); boolean bRetry = true; try { // create our EMF with our PU set timeout property emf = OpenJPAPersistence.createEntityManagerFactory( "qtimeout-0msecs", "persistence3.xml"); assertNotNull(emf); // verify PU properties updated the config OpenJPAConfiguration conf = emf.getConfiguration(); assertNotNull(conf); assertEquals("PU provided no query timeout", setTime.intValue(), conf.getQueryTimeout()); // create EM and Query em = emf.createEntityManager(); assertNotNull(em); // Following fails to cause a SQLException, but takes 2+ secs // Query q = em.createQuery("UPDATE QTimeout q SET q.stringField = // + ":strVal WHERE q.id > 0"); // q.setParameter("strVal", new String("updated")); // Following fails to cause a SQLException, but takes 2+ secs // Query q = em.createNativeQuery("INSERT INTO QTimeout (id, " + // "stringField) VALUES (?,?)"); // q.setParameter(1, 99); // q.setParameter(2, new String("inserted")); OpenJPAQuery q = em.createNativeQuery(nativeUpdateStr); q.setParameter(1, new String("updated")); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the query timeout value and verify it was set setTime = 1000; getLog().trace("testQueryTimeout33c() - Setting hint " + "javax.persistence.query.timeout=" + setTime); q.setHint("javax.persistence.query.timeout", setTime); hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = (Integer) hints.get( "javax.persistence.query.timeout"); assertEquals(timeout, setTime); // verify internal config values were updated assertEquals("PU provided query timeout", setTime.intValue(), q.getFetchPlan().getQueryTimeout()); em.getTransaction().begin(); for (int i=0; i<=1 && bRetry; i++) { try { long startTime = System.currentTimeMillis(); @SuppressWarnings("unused") int count = q.executeUpdate(); em.getTransaction().commit(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout33c() - executeUpdate " + "runTime msecs=" + runTime); fail("QueryTimeout for executeUpdate failed to cause an " + "Exception in testQueryTimeout33c(" + setTime + " mscs), runTime msecs=" + runTime); } catch (Exception e) { // expected - Should cause a QueryTimeoutException for: // Derby, MS SQL // should only retry the statement if db did not rollback // which should be a QueryTimeoutException bRetry = checkException("testQueryTimeout33c()", e); // we're only retrying to prove the right exception type // was thrown and the transaction wasn't rolled back if (bRetry) getLog().trace("testQueryTimeout33c() - retrying... "); } } } finally { if ((em != null) && em.isOpen()) { if (em.getTransaction().isActive()) em.getTransaction().rollback(); em.close(); } } } /** * Scenario being tested: 4) Timeout of -1 should be treated the same * as the default no timeout scenario. * Expected Results: The DELAY function is being called and the query * takes 2000+ msecs to complete. */ public void testQueryTimeout4() { if (skipTests) { getLog().trace("testQueryTimeout4 - test is " + "being skipped for " + dict.platform); return; } Integer setTime = new Integer(-1); getLog().trace("testQueryTimeout4() - setHint(" + setTime + ")"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createNamedQuery("NoHintSingle"); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the timeout value to -1 and verify it was set getLog().trace("testQueryTimeout4() - Setting hint " + "javax.persistence.query.timeout=" + setTime); q.setHint("javax.persistence.query.timeout", setTime); hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = (Integer) hints.get( "javax.persistence.query.timeout"); getLog().trace("testQueryTimeout4() - Retrieved hint " + "javax.persistence.query.timeout=" + timeout); assertEquals(timeout, setTime); try { long startTime = System.currentTimeMillis(); Object result = q.getSingleResult(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace( "testQueryTimeout4() - NoHintSingle runTime msecs=" + runTime); // Hack - Windows sometimes returns 1999 instead of 2000+ assertTrue("Should have taken 2+ secs, but was msecs=" + runTime, runTime > 1900); assertNotNull("Verify we received a result.", result); } catch (Exception e) { fail("Unexpected testQueryTimeout4() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 5) Setting timeout to < -1 should throw an * IllegalArgumentExpection * Expected Results: IllegalArgumentException */ public void testQueryTimeout5() { Integer setTime = new Integer(-2000); getLog().trace("testQueryTimeout5() - setHint(" + setTime + ")"); EntityManager em = null; try { em = emf.createEntityManager(); assertNotNull(em); Query q = em.createNamedQuery("NoHintSingle"); // verify no default javax.persistence.query.timeout is supplied Map<String, Object> hints = q.getHints(); assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the timeout value to -2000 and verify it was set getLog().trace("testQueryTimeout5() - Setting hint " + "javax.persistence.query.timeout=" + setTime); q.setHint("javax.persistence.query.timeout", setTime); fail("Expected testQueryTimeout5() to throw a " + "IllegalArgumentException"); } catch (Exception e) { // expected - setHint(-2000) should cause IllegalArgumentException checkException("testQueryTimeout5()", e, IllegalArgumentException.class, null); } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Scenario being tested: 6) PU Query timeout hints do not affect EM * operations like updating Entities returned by EM.find()/findAll() * Expected Results: The DELAY function is being called and the update * takes 2000+ msecs to complete. */ public void testQueryTimeout6() { if (skipTests) { getLog().trace("testQueryTimeout6 - test is " + "being skipped for " + dict.platform); return; } getLog().trace("testQueryTimeout6() - No EM.find() update timeout"); OpenJPAEntityManagerFactory emf = null; OpenJPAEntityManager em = null; Integer setTime = new Integer(1000); try { // create our EMF with our PU set timeout property emf = OpenJPAPersistence.createEntityManagerFactory( "qtimeout-1000msecs", "persistence3.xml"); assertNotNull(emf); // verify PU properties updated the config OpenJPAConfiguration conf = emf.getConfiguration(); assertNotNull(conf); assertEquals("PU provided timeout", setTime.intValue(), conf.getQueryTimeout()); // create EM em = emf.createEntityManager(); assertNotNull(em); try { long startTime = System.currentTimeMillis(); QTimeout qt = em.find(QTimeout.class, new Integer(1)); em.getTransaction().begin(); qt.setStringField("updated"); em.flush(); em.getTransaction().commit(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace("testQueryTimeout6() - EM find/update runTime" + " msecs=" + runTime); // Hack - Windows sometimes returns 1999 instead of 2000+ assertTrue("Should have taken 2+ secs, but was msecs=" + runTime, runTime > 1900); em.clear(); qt = em.find(QTimeout.class, new Integer(1)); assertEquals("Verify the entity was updated.", qt.getStringField(), "updated"); } catch (Exception e) { // setting a timeout property via PU or Map shouldn't cause a // timeout exception fail("Unexpected testQueryTimeout6() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Internal setup convenience method to create some entities for testing */ private void setupCreateEntities() { getLog().trace("setupCreateEntities()"); String[] _strings = new String[] { "a", "b", "c" }; QTimeout qt = null; EntityManager em = null; // create some initial entities try { em = emf.createEntityManager(); assertNotNull(em); getLog().trace("setupCreateEntities() - creating 3 Qtimeout"); em.getTransaction().begin(); for (int i = 0; i < _strings.length; i++) { qt = new QTimeout(i, _strings[i]); em.persist(qt); } em.getTransaction().commit(); } catch (Exception e) { fail("setupCreateEntities() - Unexpected Exception - " + e); } finally { if ((em != null) && em.isOpen()) { em.close(); } } } /** * Create delay function only on Derby, other DBs require manual setup */ private void setupCreateDBFunction() { if (dict instanceof DerbyDictionary) { getLog().trace("setupCreateDBFunction()"); // remove existing function if it exists and recreate try { exec(true, 0, "DROP TRIGGER t1"); exec(true, 0, "DROP FUNCTION DELAY"); exec(false, 0, "CREATE FUNCTION DELAY(SECONDS INTEGER, " + "VALUE INTEGER) RETURNS INTEGER PARAMETER STYLE JAVA " + "NO SQL LANGUAGE JAVA EXTERNAL NAME " + "'org.apache.openjpa.persistence." + "query.TestQueryTimeout.delay'"); } catch (SQLException sqe) { fail(sqe.toString()); } } else { getLog().trace("setupCreateDBFunction() - skipping as DB != Derby"); } } /** * Create triggers on all DBs * @param dict * @return boolean - true if successful, otherwise false */ private boolean setupCreateDBTriggers() { boolean brc = false; getLog().trace("setupCreateDBTriggers()"); // create triggers on all DBs try { if ((dict instanceof DerbyDictionary) || (dict instanceof DB2Dictionary)) { getLog().trace("setupCreateDBTriggers() - creating BEFORE " + "TRIGGER for Derby and DB2"); exec(true, 0, "DROP TRIGGER t1"); // DB2 needs multiple connections and longer delays to timeout exec(false, 0, "CREATE TRIGGER t1 NO CASCADE BEFORE UPDATE " + "ON qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-1)"); // exec(true, 0, "DROP TRIGGER t2"); // exec(false, 0, "CREATE TRIGGER t2 NO CASCADE BEFORE " + // "INSERT ON qtimeout FOR EACH ROW MODE DB2SQL " + // "values DELAY(2,-2)"); // Don't include a DELETE trigger - slows down the DROP_TABLES // cleanup between tests // exec(false, 0, "CREATE TRIGGER t3 NO CASCADE BEFORE DELETE "+ // "ON qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-3)"); brc = true; } else if (dict instanceof SQLServerDictionary) { // These may have already been created by the DDL getLog().trace("setupCreateDBTriggers() - creating BEFORE " + "UPDATE TRIGGER for MS SQL Server"); exec(true, 0, "DROP TRIGGER t1"); exec(false, 0, "CREATE TRIGGER t1 ON QTimeout FOR UPDATE " + "AS EXTERNAL NAME Delay.Delay.delay"); // exec(true, 0, "DROP TRIGGER t2"); // exec(false, 0, "CREATE OR REPLACE TRIGGER t2 ON QTimeout " + // "FOR INSERT AS EXTERNAL NAME Delay.Delay.delay"); brc = true; } else if (dict instanceof OracleDictionary) { // These may have already been created by the DDL getLog().trace("setupCreateDBTriggers() - creating BEFORE " + "UPDATE TRIGGER for Oracle"); exec(false, 0, "CREATE OR REPLACE TRIGGER \"OPENJPA\".T1 " + "BEFORE UPDATE OF \"ID\", \"STRINGFIELD\", " + "\"VERSIONFIELD\" ON \"OPENJPA\".\"QTIMEOUT\" CALL DELAY"); // exec(false, 0, "CREATE OR REPLACE TRIGGER \"OPENJPA\".T2 " + // "BEFORE INSERT ON \"OPENJPA\".\"QTIMEOUT\" CALL DELAY"); brc = true; } else if (dict instanceof InformixDictionary) { // These may have already been created by the DDL getLog().trace("setupCreateDBTriggers() - creating BEFORE " + "UPDATE TRIGGER for Informix"); exec(true, 0, "DROP TRIGGER t1"); // exec(false, 0, "CREATE TRIGGER t1 UPDATE " + // "of Id, stringField, versionField ON QTimeout " + // "BEFORE(EXECUTE FUNCTION delay(5,-1))"); exec(false, 0, "CREATE TRIGGER t1 UPDATE " + "of Id, stringField, versionField ON QTimeout " + "BEFORE(EXECUTE PROCEDURE delay5())"); brc = true; } else { getLog().info("TestQueryTimeout tests are being skipped as " + "triggers were not created for " + dict.platform); } } catch (SQLException sqe) { if (dict instanceof DerbyDictionary) { // Always fail if we couldn't create triggers in Derby fail(sqe.toString()); } else { // just disable tests for other DBs getLog().info("TestQueryTimeout tests are being skipped, " + "due to DB delay() function missing and/or problems " + "creating the required triggers. " + dict.platform + " requires manual setup steps for these tests."); getLog().trace("setupCreateDBTriggers() failed with " + "SQLException = ", sqe); } } return brc; } /** * Internal convenience method to execute SQL statements * * @param em * @param sql * @param timeoutSecs * @param fail */ private void exec(boolean ignoreExceptions, int timeoutSecs, String sql) throws SQLException { EntityManager em = null; Statement s = null; try { em = emf.createEntityManager(); assertNotNull(em); Broker broker = JPAFacadeHelper.toBroker(em); Connection conn = (Connection) broker.getConnection(); s = conn.createStatement(); if (timeoutSecs > 0) { s.setQueryTimeout(timeoutSecs); } getLog().trace("execute(" + sql + ")"); s.execute(sql); } catch (SQLException sqe) { if (!ignoreExceptions) { // fail(sqe.toString()); throw sqe; } } finally { if (s != null) { try { s.close(); } catch (Exception e) { // ignore } } if ((em != null) && em.isOpen()) { em.close(); } } } /** * Internal convenience method for checking that the given Exception matches * the expected type for a given DB platform. * * @param test * @param e * @return boolean indicating if operation can be retried */ private boolean checkException(String test, Exception e) { String eStr = new String("query statement timeout"); boolean bRetry = matchesExpectedException(QueryTimeoutException.class, e, eStr); boolean bRollback = matchesExpectedException(PersistenceException.class, e, eStr); // 31c and 33c fail on MSSQL2005, so this is a special case to handle it boolean bLockFailed = matchesExpectedException(OptimisticLockException.class, e, "Unable to obtain an object lock"); // no easy way to determine exact Exception type for all DBs assertTrue(test + " - UNEXPECTED Exception = " + e, (bRetry || bRollback || bLockFailed)); getLog().trace(test + " - Caught expected Exception = ", e); return bRetry; } /** * Internal convenience method for checking that the given Exception matches * the expected type. * * @param test case name * @param tested exception type * @param expected exception type * @param eStr an optional substring to match in the exception text */ private void checkException(String test, Exception tested, Class<?> expected, String eStr) { assertTrue(test + " - UNEXPECTED Exception = " + tested, matchesExpectedException(expected, tested, eStr)); getLog().trace(test + " - Caught expected Exception = ", tested); } /** * Internal convenience method for checking that the given Exception matches * the expected type. * * @param expected * @param tested * @param eStr an optional substring to match in the exception text * @return true if the exception matched, false otherwise */ private boolean matchesExpectedException(Class<?> expected, Exception tested, String eStr) { assertNotNull(expected); boolean exMatched = false; if (tested != null) { Class<?> testExClass = tested.getClass(); exMatched = expected.isAssignableFrom(testExClass); if (exMatched && eStr != null) { // make sure it is our expected exception text from // localizer.properties exMatched = (tested.getMessage().indexOf(eStr) != -1); } } return exMatched; } /** * This is the user-defined DB FUNCTION which is called from our queries to * sleep and cause timeouts, based on seconds. * * @param secs * @param value * @return value * @throws SQLException */ public static int delay(int secs, int value) throws SQLException { try { /* if (value >= 0) { System.out.println(" Native SQL called delay(secs=" + secs + ",value=" + value + ")"); } else { System.out.println(" Trigger called delay(secs=" + secs + ",value=" + value + ")"); } */ Thread.sleep(secs * 1000); } catch (InterruptedException e) { // Ignore } return value; } }