package org.apache.torque.util;
/*
* 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.
*/
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.torque.BaseDatabaseTestCase;
import org.apache.torque.ConstraintViolationException;
import org.apache.torque.DeadlockException;
import org.apache.torque.Torque;
import org.apache.torque.TorqueException;
import org.apache.torque.adapter.Adapter;
import org.apache.torque.adapter.HsqldbAdapter;
import org.apache.torque.criteria.Criteria;
import org.apache.torque.test.dbobject.Author;
import org.apache.torque.test.dbobject.Book;
import org.apache.torque.test.dbobject.SingleNamedUnique;
import org.apache.torque.test.peer.SingleNamedUniquePeer;
public class ExceptionMapperTest extends BaseDatabaseTestCase
{
/** Sleep time for thread polling, in miliseconds. */
private static int SLEEP_TIME = 10;
/** Timeout for waiting for started threads and saves, in miliseconds. */
private static int TIMEOUT = 50000;
private static Log log = LogFactory.getLog(ExceptionMapperTest.class);
/**
* Check that a ConstraintViolationException is thrown if a record
* with an already-existing primary key is inserted.
*/
public void testInsertWithPkViolation() throws TorqueException
{
// prepare
cleanBookstore();
List<Author> bookstoreContent = insertBookstoreData();
Author author = new Author();
author.setName("Author");
author.setAuthorId(bookstoreContent.get(0).getAuthorId());
// execute & verify
try
{
author.save();
fail("Exception expected");
}
catch (ConstraintViolationException e)
{
assertTrue(e.getCause() instanceof SQLException);
}
}
/**
* Check that a ConstraintViolationException is thrown if a record
* with an already-existing unique key is inserted.
*/
public void testUpdateWithUniqueConstraintViolation() throws TorqueException
{
// prepare
SingleNamedUniquePeer.doDelete(new Criteria());
SingleNamedUnique unique = new SingleNamedUnique();
unique.setValue(1);
unique.save();
SingleNamedUnique duplicateUnique = new SingleNamedUnique();
duplicateUnique.setValue(1);
// execute & verify
try
{
duplicateUnique.save();
fail("Exception expected");
}
catch (ConstraintViolationException e)
{
assertTrue(e.getCause() instanceof SQLException);
}
}
/**
* Check that a ConstraintViolationException is thrown if a record
* is saved which contains null in a non-nullable column
*/
public void testInsertWithNonNullableColumnViolation()
throws TorqueException
{
// prepare
cleanBookstore();
Author author = new Author();
author.setName(null); // is required
// execute & verify
try
{
author.save();
fail("Exception expected");
}
catch (ConstraintViolationException e)
{
assertTrue(e.getCause() instanceof SQLException);
}
}
/**
* Check that a ConstraintViolationException is thrown if a record
* is saved which has a foreign key to a non-existing object
*/
public void testInsertWithForeignKeyConstraintViolation()
throws TorqueException
{
// prepare
cleanBookstore();
Book book = new Book();
book.setTitle("title");
book.setIsbn("isbn");
book.setAuthorId(1); // does not exist
// execute & verify
try
{
book.save();
fail("Exception expected");
}
catch (ConstraintViolationException e)
{
assertTrue(e.getCause() instanceof SQLException);
}
}
/**
* Check that a deadlockExcetion is thrown if two transaction want to access
* a resource locked by the other thread.
*
* @throws Exception if an error occurs.
*/
public void testTransactionDeadlock() throws Exception
{
Adapter adapter
= Torque.getDatabase(Torque.getDefaultDB()).getAdapter();
if (adapter instanceof HsqldbAdapter)
{
log.warn("hsqldb (2.2.6) fails to detect the deadlock in this test"
+ " therefore this test is skipped");
return;
}
// prepare
cleanBookstore();
List<Author> bookstoreContent = insertBookstoreData();
Connection transaction1 = null;
Connection transaction2 = null;
try
{
// lock author 1 in transaction 1
transaction1 = Transaction.begin();
if (transaction1.getAutoCommit())
{
fail("autocommit must be set to false for this test");
}
Author author1 = bookstoreContent.get(0);
author1.setName("new Author1");
author1.save(transaction1);
// lock author 2 in transaction 2
transaction2 = Transaction.begin();
if (transaction2.getAutoCommit())
{
fail("autocommit must be set to false for this test(2)");
}
Author author2 = bookstoreContent.get(1);
author2.setName("new Author2");
author2.save(transaction2);
// lock author 2 in transaction 1 (must wait for lock)
author2.setName("newer Author2");
SaveAndRollbackThread saveThreadTransaction1
= new SaveAndRollbackThread(
author2,
transaction1,
"saveThreadAuthor2Con1");
saveThreadTransaction1.start();
long startTime = System.currentTimeMillis();
while (!author2.isSaving() && author2.isModified()
&& saveThreadTransaction1.isAlive())
{
Thread.sleep(SLEEP_TIME);
if (System.currentTimeMillis() > startTime + TIMEOUT)
{
fail("Waited too long for saving to start");
}
}
// Try to lock author1 in transaction 2 (deadlock)
author1.setName("newer Author1");
SaveAndRollbackThread saveThreadTransaction2
= new SaveAndRollbackThread(
author1,
transaction2,
"saveThreadAuthor1Con2");
saveThreadTransaction2.start();
startTime = System.currentTimeMillis();
while (!author1.isSaving() && author1.isModified()
&& saveThreadTransaction2.isAlive())
{
Thread.sleep(SLEEP_TIME);
if (System.currentTimeMillis() > startTime + TIMEOUT)
{
fail("Waited too long for saving to start (2)");
}
}
// wait till save on transaction 1 has finished
startTime = System.currentTimeMillis();
while (saveThreadTransaction1.isAlive())
{
Thread.sleep(10);
if (System.currentTimeMillis() > startTime + TIMEOUT)
{
fail("Waited too long for saving to finish");
}
}
// wait till save on transaction 2 has finished
startTime = System.currentTimeMillis();
while (saveThreadTransaction2.isAlive())
{
Thread.sleep(10);
if (System.currentTimeMillis() > startTime + TIMEOUT)
{
fail("Waited too long for saving to finish (2)");
}
}
// verify. Either in transaction 1 or in transaction 2
// a deadlock exception must have occurred
if (saveThreadTransaction1.getCaughtException() != null)
{
if (!(saveThreadTransaction1.getCaughtException()
instanceof DeadlockException))
{
throw saveThreadTransaction1.getCaughtException();
}
}
else if (saveThreadTransaction2.getCaughtException() == null)
{
fail("No Deadlock occured");
if (!(saveThreadTransaction2.getCaughtException()
instanceof DeadlockException))
{
throw saveThreadTransaction2.getCaughtException();
}
}
}
finally
{
Transaction.safeRollback(transaction1);
Transaction.safeRollback(transaction2);
}
}
private static class SaveAndRollbackThread extends Thread
{
private final Author toSave;
private final Connection transaction;
private TorqueException caughtException;
public SaveAndRollbackThread(
Author toSave,
Connection transaction,
String name)
{
this.toSave = toSave;
this.transaction = transaction;
this.setName(name);
}
@Override
public void run()
{
try
{
log.debug(getName() + "Starting to save author");
toSave.save(transaction);
log.debug(getName() + "Finished saving author");
}
catch (TorqueException e)
{
log.debug(getName() + "caught exception "
+ e.getClass().getName());
caughtException = e;
}
finally
{
log.debug(getName() + "starting rollback");
Transaction.safeRollback(transaction);
log.debug(getName() + "rollback finished");
}
}
public TorqueException getCaughtException()
{
return caughtException;
}
}
}