/*
* Copyright 2009 Ralf Joachim
*
* Licensed 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.castor.cpa.test.test75;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.cpa.test.framework.CPATestCase;
import org.castor.cpa.test.framework.xml.types.DatabaseEngineType;
import org.exolab.castor.jdo.CacheManager;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.QueryResults;
/**
* Expire Cache test. Tests the ability to clear objects from the cache. This
* includes clearing objects by class or type, or individually, by
* object identities.
*/
public final class TestExpireManyToMany extends CPATestCase {
/** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
* Commons Logging</a> instance used for all logging. */
private static final Log LOG = LogFactory.getLog(TestExpireManyToMany.class);
private static final String DBNAME = "test75";
private static final String MAPPING = "/org/castor/cpa/test/test75/mapping.xml";
private static final boolean BY_TYPE_OR_CLASS = true;
private static final boolean BY_OBJECT_IDENTITY = false;
private static final String JDO_ORIGINAL_VALUE = "Original JDO String";
private static final String JDO_UPDATED_VALUE = "Updated Using JDO";
private static final String JDBC_UPDATED_VALUE = "Updated Using JDBC";
private Database _db;
private Connection _conn;
private PreparedStatement _updateGroupStatement;
private PreparedStatement _updatePersonStatement;
private int _groupAId = 201, _groupBId = 202, _groupCId = 203, _groupDId = 204;
private int _person1Id = 1, _person2Id = 2, _person3Id = 3, _person4Id = 4;
/**
* Constructor
*/
public TestExpireManyToMany(final String name) {
super(name);
}
// Test are only included/excluded for engines that have been tested with this test suite.
public boolean include(final DatabaseEngineType engine) {
return (engine == DatabaseEngineType.DERBY)
|| (engine == DatabaseEngineType.HSQL)
|| (engine == DatabaseEngineType.MYSQL)
|| (engine == DatabaseEngineType.ORACLE)
|| (engine == DatabaseEngineType.POSTGRESQL);
}
/**
* Initializes fields
*/
public void setUp() throws Exception {
JDOManager jdo = getJDOManager(DBNAME, MAPPING);
_db = jdo.getDatabase();
_conn = jdo.getConnectionFactory().createConnection();
// initialze JDBC connection
try {
_updateGroupStatement = _conn.prepareStatement(
"update test75_group set value1=? where gid=?");
_updatePersonStatement = _conn.prepareStatement(
"update test75_person set value1=? where pid=?");
} catch (java.sql.SQLException e) {
fail("Failed to establish JDBC Connection");
}
}
/**
* Close the database and JDBC connection
*/
public void tearDown() throws PersistenceException, SQLException {
if (_db.isActive()) { _db.rollback(); }
_db.close();
_conn.close();
}
/**
* Calls the individual tests embedded in this test case
*/
public void testExpireCache() {
testExpireCache(BY_OBJECT_IDENTITY);
testExpireCache(BY_TYPE_OR_CLASS);
}
/**
* Test the expire cache logic. This method includes steps to 1) create a
* test data set, 2) enter read and write transactions (to test the proper
* execution of the expire cache logic while transactions are in progress),
* 3) update several objects outside of JDO using JDBC, 5) validate that
* subsequent read/write operations on the objects expired from the cache
* actually reflect values from the backend database, and, finally,
* 6) remove the test data set.
*
* @param expireByType when <code>true</code> this method will expire
* objects from the cache by specifying a single type or class of
* objects to be expired; when <code>false</code> this method will
* expire objects from the cache using individual object identities
*/
public void testExpireCache(final boolean expireByType) {
LOG.debug("starting testExpireCache "
+ (expireByType ? "by type" : "by object identity"));
try {
// delete any data left over from previous tests
deleteTestDataSet();
try {
createTestDataSet();
} catch (Exception e) {
// attempt to delete any test data that was created.
deleteTestDataSet();
fail("Failed to create test data set");
}
// "prime" the cache
validReadTransaction(_groupAId, JDO_ORIGINAL_VALUE, true);
validReadTransaction(_groupBId, JDO_ORIGINAL_VALUE, true);
validReadTransaction(_groupCId, JDO_ORIGINAL_VALUE, true);
validReadTransaction(_groupDId, JDO_ORIGINAL_VALUE, true);
// now update the database outside of JDO
updateGroupUsingJDBC(_groupAId);
updateGroupUsingJDBC(_groupBId);
updateGroupUsingJDBC(_groupCId);
updateGroupUsingJDBC(_groupDId);
updatePersonUsingJDBC(_person1Id);
updatePersonUsingJDBC(_person2Id);
updatePersonUsingJDBC(_person3Id);
updatePersonUsingJDBC(_person4Id);
// and force the cache to expire
expire(expireByType);
// validate that cached field values are not used in
// subsequent read/write operations
boolean success = true;
if (!validReadTransaction(_groupAId, JDBC_UPDATED_VALUE, true)) {
success = false;
}
if (!validWriteTransaction(_groupAId)) { success = false; }
// don't validate people because they should now be cached with the
// JDO_UPDATE_VALUE and won't pass the following validReadTransaction tests.
if (!validReadTransaction(_groupBId, JDBC_UPDATED_VALUE, false)) {
success = false;
}
if (!validWriteTransaction(_groupBId)) { success = false; }
if (!validReadTransaction(_groupCId, JDBC_UPDATED_VALUE, false)) {
success = false;
}
if (!validWriteTransaction(_groupCId)) { success = false; }
if (!validReadTransaction(_groupDId, JDBC_UPDATED_VALUE, false)) {
success = false;
}
if (!validWriteTransaction(_groupDId)) { success = false; }
if (success) {
LOG.debug("Test Completed Successfully.");
} else {
fail("Cache was not properly expired");
}
deleteTestDataSet();
} catch (Exception e) {
LOG.warn("ERROR: fatal exception encountered during test", e);
fail("Exception encountered during test");
}
}
/**
* Create the requisite objects in the database
*/
private void createTestDataSet() throws Exception {
LOG.debug("creating test data set...");
ManyGroup groupA, groupB, groupC, groupD;
ManyPerson person1, person2, person3, person4;
ArrayList<ManyPerson> al;
ArrayList<ManyPerson> bl;
ArrayList<ManyPerson> c1;
ArrayList<ManyPerson> d1;
Database db = null;
try {
db = getJDOManager(DBNAME, MAPPING).getDatabase();
db.begin();
//
// create four persons
//
person1 = new ManyPerson();
ArrayList<ManyGroup> gPerson1 = new ArrayList<ManyGroup>();
person1.setId(_person1Id);
person1.setGroup(gPerson1);
person1.setSthelse("Something else");
person1.setHelloworld("Hello World!");
person1.setValue1(JDO_ORIGINAL_VALUE);
person2 = new ManyPerson();
ArrayList<ManyGroup> gPerson2 = new ArrayList<ManyGroup>();
person2.setId(_person2Id);
person2.setGroup(gPerson2);
person2.setSthelse("Something else");
person2.setHelloworld("Hello World!");
person2.setValue1(JDO_ORIGINAL_VALUE);
person3 = new ManyPerson();
ArrayList<ManyGroup> gPerson3 = new ArrayList<ManyGroup>();
person3.setId(_person3Id);
person3.setGroup(gPerson3);
person3.setSthelse("Something else for person 3");
person3.setHelloworld("Hello World!");
person3.setValue1(JDO_ORIGINAL_VALUE);
person4 = new ManyPerson();
ArrayList<ManyGroup> gPerson4 = new ArrayList<ManyGroup>();
person4.setId(_person4Id);
person4.setGroup(gPerson4);
person4.setSthelse("Something else for person 4");
person4.setHelloworld("Hello World!");
person4.setValue1(JDO_ORIGINAL_VALUE);
//
// create four groups, assign all persons to each group
//
groupA = new ManyGroup();
groupA.setValue1(JDO_ORIGINAL_VALUE);
al = new ArrayList<ManyPerson>();
al.add(person1);
al.add(person2);
al.add(person3);
al.add(person4);
groupA.setId(_groupAId);
groupA.setPeople(al);
groupB = new ManyGroup();
groupB.setValue1(JDO_ORIGINAL_VALUE);
groupB.setId(_groupBId);
bl = new ArrayList<ManyPerson>();
bl.add(person1);
bl.add(person2);
bl.add(person3);
bl.add(person4);
groupB.setPeople(bl);
groupC = new ManyGroup();
groupC.setValue1(JDO_ORIGINAL_VALUE);
c1 = new ArrayList<ManyPerson>();
c1.add(person1);
c1.add(person2);
c1.add(person3);
c1.add(person4);
groupC.setId(_groupCId);
groupC.setPeople(c1);
groupD = new ManyGroup();
groupD.setValue1(JDO_ORIGINAL_VALUE);
d1 = new ArrayList<ManyPerson>();
d1.add(person1);
d1.add(person2);
d1.add(person3);
d1.add(person4);
groupD.setId(_groupDId);
groupD.setPeople(d1);
//
// assign all groups to each person
//
gPerson1.add(groupA);
gPerson1.add(groupB);
gPerson1.add(groupC);
gPerson1.add(groupD);
gPerson2.add(groupA);
gPerson2.add(groupB);
gPerson2.add(groupC);
gPerson2.add(groupD);
gPerson3.add(groupA);
gPerson3.add(groupB);
gPerson3.add(groupC);
gPerson3.add(groupD);
gPerson4.add(groupA);
gPerson4.add(groupB);
gPerson4.add(groupC);
gPerson4.add(groupD);
//
// create persistent groups and persons
//
db.create(person1);
db.create(person2);
db.create(person3);
db.create(person4);
db.create(groupA);
db.create(groupB);
db.create(groupC);
db.create(groupD);
db.commit();
} catch (Exception e) {
LOG.warn("createTestDataSet: exception caught: " + e.getMessage());
throw e;
}
}
/**
* Update an object outside of JDO using a JDBC connection.
*
* @param groupId primary key of object to be updated
*/
private void updateGroupUsingJDBC(final int groupId) {
LOG.debug("updating group " + groupId + " using JDBC...");
try {
_updateGroupStatement.setString(1, JDBC_UPDATED_VALUE);
_updateGroupStatement.setInt(2, groupId);
int rc = _updateGroupStatement.executeUpdate();
if (rc <= 0) {
LOG.warn("updateGroupUsingJDBC: error updating group "
+ groupId + ", return code = " + rc);
}
} catch (Exception e) {
LOG.warn("updateGroupUsingJDBC: exception updating group "
+ groupId + ": " + e.getMessage(), e);
}
}
/**
* Update an object outside of JDO using a JDBC connection.
*
* @param personId primary key of object to be updated
*/
private void updatePersonUsingJDBC(final int personId) {
LOG.debug("updating person " + personId + " using JDBC...");
try {
_updatePersonStatement.setString(1, JDBC_UPDATED_VALUE);
_updatePersonStatement.setInt(2, personId);
int rc = _updatePersonStatement.executeUpdate();
if (rc <= 0) {
LOG.warn("updatePersonUsingJDBC: error updating person "
+ personId + ", return code = " + rc);
return;
}
} catch (Exception e) {
LOG.warn("updatePersonUsingJDBC: exception updating person "
+ personId + ": " + e.getMessage(), e);
}
}
/**
* Setup and execute a database expire cache request.
*
* @param byType when <code>true</code> this method will request that
* objects be expired from the cache by specifying a single type or
* class of objects to be expired; when <code>false</code> this method
* will request that objects be expired from the cache using individual
* object identities
*/
private void expire(final boolean byType) {
LOG.debug("expiring cache...");
Object[] identityArray = null;
try {
CacheManager cacheManager = _db.getCacheManager();
if (byType) {
Class<?>[] typeArray = new Class[2];
typeArray[0] = ManyGroup.class;
typeArray[1] = ManyPerson.class;
cacheManager.expireCache(typeArray);
} else {
identityArray = new Object[4];
identityArray[0] = new Integer(_groupAId);
identityArray[1] = new Integer(_groupBId);
identityArray[2] = new Integer(_groupCId);
identityArray[3] = new Integer(_groupDId);
cacheManager.expireCache(ManyGroup.class, identityArray);
}
} catch (Exception e) {
LOG.warn("expireCache: exception encountered clearing cache: " + e.getMessage());
}
}
/**
* Read, or load, an object and validate that the field values
* reflect values updated outside of JDO and not from previously
* cached values.
*
* @param groupId primary key of object to be read
*/
private boolean validReadTransaction(final int groupId,
final String expectedValue, final boolean checkPeople) {
LOG.debug("validating read transaction for group " + groupId + "...");
Database db = null;
boolean valid = true;
try {
db = getJDOManager(DBNAME, MAPPING).getDatabase();
db.begin();
ManyGroup group = db.load(ManyGroup.class, new Integer(groupId));
if (group.getValue1().compareTo(expectedValue) != 0) {
LOG.warn("validReadTransaction: value in group " + group.getId()
+ " does not match expected value, value: " + group.getValue1()
+ ", expected: " + expectedValue);
valid = false;
}
if (checkPeople) {
Iterator<ManyPerson> itor = group.getPeople().iterator();
while (itor.hasNext()) {
ManyPerson person = itor.next();
if (person.getValue1().compareTo(expectedValue) != 0) {
LOG.warn("validReadTransaction: value in person " + person.getId()
+ " does not match expected value, value: "
+ person.getValue1() + ", expected: " + expectedValue);
valid = false;
}
}
}
db.rollback();
db.close();
} catch (Exception e) {
if (db != null) {
try {
db.close();
} catch (Exception se) {
LOG.warn("Problem closing Database instance.", se);
}
}
LOG.warn("validReadTransaction: exception caught while validating read for group "
+ groupId + " : " + e.getMessage(), e);
valid = false;
}
return valid;
}
/**
* Modify fields of an object and validate that the transaction completes
* successfully.
*
* @param groupId primary key of object to be updated
*/
private boolean validWriteTransaction(final int groupId) {
LOG.debug("validating write transaction for group " + groupId + "...");
Database db = null;
try {
db = getJDOManager(DBNAME, MAPPING).getDatabase();
db.begin();
ManyGroup group = db.load(ManyGroup.class, new Integer(groupId));
group.setValue1(JDO_UPDATED_VALUE);
Iterator<ManyPerson> itor = group.getPeople().iterator();
while (itor.hasNext()) {
ManyPerson person = itor.next();
person.setValue1(JDO_UPDATED_VALUE);
}
db.commit();
db.close();
} catch (Exception e) {
if (db != null) {
try {
db.close();
} catch (Exception se) {
LOG.warn("Problem closing Database instance.", se);
}
}
LOG.warn("validWriteTransaction: exception caught while validating group write "
+ groupId + " : " + e.getMessage(), e);
return false;
}
return true;
}
/**
* Delete all objects in test data set.
*/
private void deleteTestDataSet() {
LOG.debug("deleting test data set...");
QueryResults enumeration;
ManyGroup group = null;
ManyPerson person = null;
Database db = null;
try {
// select an group and delete it, if it exist!
db = getJDOManager(DBNAME, MAPPING).getDatabase();
db.begin();
OQLQuery oqlclean = db.getOQLQuery(
"SELECT object FROM " + ManyGroup.class.getName()
+ " object WHERE object.id < $1");
oqlclean.bind(Integer.MAX_VALUE);
enumeration = oqlclean.execute();
while (enumeration.hasMore()) {
group = (ManyGroup) enumeration.next();
LOG.debug("Retrieved object: " + group);
db.remove(group);
LOG.debug("Deleted object: " + group);
}
db.commit();
db.begin();
oqlclean = db.getOQLQuery(
"SELECT object FROM " + ManyPerson.class.getName()
+ " object WHERE object.id < $1");
oqlclean.bind(Integer.MAX_VALUE);
enumeration = oqlclean.execute();
while (enumeration.hasMore()) {
person = (ManyPerson) enumeration.next();
LOG.debug("Retrieved object: " + person);
db.remove(person);
LOG.debug("Deleted object: " + person);
}
db.commit();
} catch (Exception e) {
if (db != null) {
try {
db.close();
} catch (Exception se) {
LOG.warn("Problem closing Database instance.", se);
}
}
LOG.warn("deleteTestDataSet: exception caught: " + e.getMessage(), e);
}
}
}