/* * Copyright 2008 Udai Gupta * * 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.test89; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.Statement; 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.persist.spi.Identity; /** * 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 TestLazyEmployeeExpiration extends CPATestCase { private static final Log LOG = LogFactory .getLog(TestLazyEmployeeExpiration.class); private static final boolean BY_TYPE_OR_CLASS = true; private static final boolean BY_OBJECT_IDENTITY = false; private static final Date JDO_ORIGINAL_DATE = Date.valueOf("2000-01-02"); private static final Date JDO_UPDATED_DATE = Date.valueOf("2001-03-04"); private static final Date JDBC_UPDATED_DATE = Date.valueOf("2002-05-06"); private static final String JDO_ORIGINAL_STRING = "Original JDO String"; private static final String JDO_UPDATED_STRING = "Updated Using JDO"; private static final String JDBC_UPDATED_STRING = "Updated Using JDBC"; private static final String DBNAME = "test89"; private static final String MAPPING = "/org/castor/cpa/test/test89/mapping.xml"; private Database _db; public TestLazyEmployeeExpiration(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); } public void setUp() throws Exception { _db = getJDOManager(DBNAME, MAPPING).getDatabase(); } public void tearDown() throws Exception { if (_db.isActive()) { _db.rollback(); } _db.close(); } public void testExpireCache() throws Exception { expireCache(BY_OBJECT_IDENTITY); expireCache(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 * @throws Exception */ public void expireCache(final boolean expireByType) throws Exception { LOG.info("starting testExpireCache " + (expireByType ? "by type" : "by object identity")); // delete any data left over from previous tests deleteTestDataSet(); createTestDataSet(); // now update the database outside of JDO updatePersonUsingJDBC("First", "Person", JDBC_UPDATED_DATE); updateEmplUsingJDBC("First", "Person", JDBC_UPDATED_DATE); updateAddrUsingJDBC(1, Integer.toString(1) + JDBC_UPDATED_STRING); updateAddrUsingJDBC(2, Integer.toString(2) + JDBC_UPDATED_STRING); updateAddrUsingJDBC(3, Integer.toString(3) + JDBC_UPDATED_STRING); // 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("First", "Person")) { success = false; } if (!validWriteTransaction("First", "Person")) { success = false; } if (success) { LOG.info("Test Completed Successfully."); } else { fail("Cache was not properly expired"); } deleteTestDataSet(); } private void createTestDataSet() throws Exception { LOG.info("creating test data set..."); _db.begin(); LazyEmployee person = new LazyEmployee(); person.setFirstName("First"); person.setLastName("Person"); person.setBirthday(JDO_ORIGINAL_DATE); person.setStartDate(JDO_ORIGINAL_DATE); LazyAddress address1 = new LazyAddress(); address1.setId(1); address1.setStreet(Integer.toString(1) + JDO_ORIGINAL_STRING); address1.setCity("First City"); address1.setState("AB"); address1.setZip("10000"); address1.setPerson(person); LazyAddress address2 = new LazyAddress(); address2.setId(2); address2.setStreet(Integer.toString(2) + JDO_ORIGINAL_STRING); address2.setCity("Second City"); address2.setState("BC"); address2.setZip("22222"); address2.setPerson(person); LazyAddress address3 = new LazyAddress(); address3.setId(3); address3.setStreet(Integer.toString(3) + JDO_ORIGINAL_STRING); address3.setCity("Third Ave"); address3.setState("AB"); address3.setZip("30003"); address3.setPerson(person); ArrayList<LazyAddress> addresslist = new ArrayList<LazyAddress>(); addresslist.add(address1); addresslist.add(address2); addresslist.add(address3); person.setAddress(addresslist); LazyPayRoll pr1 = new LazyPayRoll(); pr1.setId(1); pr1.setHoliday(15); pr1.setHourlyRate(25); pr1.setEmployee(person); person.setPayRoll(pr1); LazyContractCategory cc = new LazyContractCategory(); cc.setId(101); cc.setName("Full-time slave"); _db.create(cc); LazyContractCategory cc2 = new LazyContractCategory(); cc2.setId(102); cc2.setName("Full-time employee"); _db.create(cc2); ArrayList<LazyContractCategory> category = new ArrayList<LazyContractCategory>(); category.add(cc); category.add(cc2); LazyContract con = new LazyContract(); con.setPolicyNo(1001); con.setComment("80 hours a week, no pay hoilday, " + "no sick leave, arrive office at 7:30am everyday"); con.setContractNo(78); con.setEmployee(person); con.setCategory(category); person.setContract(con); _db.create(person); _db.commit(); } /** * Update a person outside of JDO using a JDBC connection. * * @param firstName * first part of primary key of object to be updated * @param lastName * first part of primary key of object to be updated * @param newBirthdate * new value of persons birthdate * @throws Exception */ private void updatePersonUsingJDBC(final String firstName, final String lastName, final Date newBirthdate) throws Exception { LOG.info("updatePersonUsingJDBC: updating " + firstName + " " + lastName); _db.begin(); PreparedStatement updatePersonStatement = _db .getJdbcConnection() .prepareStatement( "update test89_pks_person set bday=? where fname=? and lname=?"); updatePersonStatement.setDate(1, newBirthdate); updatePersonStatement.setString(2, firstName); updatePersonStatement.setString(3, lastName); int rc = updatePersonStatement.executeUpdate(); if (rc <= 0) { LOG.error(// "updatePersonUsingJDBC: error updating person, return = " + rc); return; } _db.commit(); } /** * Update a person outside of JDO using a JDBC connection. * * @param firstName * first part of primary key of object to be updated * @param lastName * first part of primary key of object to be updated * @param newStartDate * new value of persons birthdate * @throws Exception */ private void updateEmplUsingJDBC(final String firstName, final String lastName, final Date newStartDate) throws Exception { LOG.info("updateEmployeeUsingJDBC: updating " + firstName + " " + lastName); _db.begin(); PreparedStatement updateEmployeeStatement = _db .getJdbcConnection() .prepareStatement( "update test89_pks_employee set start_date=? where fname=? and lname=?"); updateEmployeeStatement.setDate(1, newStartDate); updateEmployeeStatement.setString(2, firstName); updateEmployeeStatement.setString(3, lastName); int rc = updateEmployeeStatement.executeUpdate(); if (rc <= 0) { LOG.error("updateEmplUsingJDBC: error updating employee, return = " + rc); return; } _db.commit(); } /** * Update an address outside of JDO using a JDBC connection. * * @param addressId * primary key of object to be updated * @param newStreet * new value of addresses street field * @throws Exception */ private void updateAddrUsingJDBC(final int addressId, final String newStreet) throws Exception { LOG.info("updateAddressUsingJDBC: updating " + addressId); _db.begin(); PreparedStatement updateAddressStatement = _db .getJdbcConnection() .prepareStatement( "update test89_pks_address set street=? where id=?"); updateAddressStatement.setString(1, newStreet); updateAddressStatement.setInt(2, addressId); int rc = updateAddressStatement.executeUpdate(); if (rc <= 0) { LOG.error(// "updateAddrUsingJDBC: error updating address, return = " + rc); return; } _db.commit(); } /** * 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.info("expiring cache..."); try { CacheManager cacheManager = _db.getCacheManager(); if (byType) { Class<?>[] typeArray = new Class[5]; typeArray[0] = LazyContract.class; typeArray[1] = LazyContractCategory.class; typeArray[2] = LazyPayRoll.class; typeArray[3] = LazyAddress.class; typeArray[4] = LazyEmployee.class; cacheManager.expireCache(typeArray); } else { Object[] identityArray = new Object[1]; identityArray[0] = new Identity("First", "Person"); cacheManager.expireCache(LazyEmployee.class, identityArray); } } catch (Exception e) { LOG.error("expireCache: exception encountered clearing cache", e); } } /** * Read, or load, an object and validate that the field values reflect * values updated outside of JDO and not from previously cached values. * * @param firstName * primary key of object to be read * @param lastName * primary key of object to be read * @return True if read transaction is valid. */ private boolean validReadTransaction(final String firstName, final String lastName) { LOG.info("validating read transaction for person " + firstName + " " + lastName + "..."); boolean valid = true; try { _db.begin(); Identity fullname = new Identity("First", "Person"); LazyEmployee person; person = _db.load(LazyEmployee.class, fullname); if (person.getBirthday().compareTo(JDBC_UPDATED_DATE) != 0) { LOG.debug("validReadTransaction: birthdate for " + person.getFirstName() + " " + person.getLastName() + " does not match expected value, value: " + person.getBirthday().toString() + ", expected: " + JDBC_UPDATED_DATE.toString()); valid = false; } if (person.getStartDate().compareTo(JDBC_UPDATED_DATE) != 0) { LOG.debug("validReadTransaction: start date for " + person.getFirstName() + " " + person.getLastName() + " does not match expected value, value: " + person.getStartDate().toString() + ", expected: " + JDBC_UPDATED_DATE.toString()); valid = false; } Iterator<LazyAddress> itor = person.getAddress().iterator(); while (itor.hasNext()) { LazyAddress address = itor.next(); String expectedStreet = new String(Integer.toString(address .getId()) + JDBC_UPDATED_STRING); if (address.getStreet().compareTo(expectedStreet) != 0) { LOG.debug("validReadTransaction: street in address " + address.getId() + " does not match expected value, value: " + address.getStreet() + ", expected: " + expectedStreet); valid = false; } } _db.rollback(); } catch (Exception e) { LOG.error( "validReadTransaction: exception while validating read for " + firstName + " " + lastName, e); valid = false; } return valid; } /** * Modify fields of an object and validate that the transaction completes * successfully. * * @param firstName * First name. * @param lastName * Last name. * @return True if write transaction is valid. */ private boolean validWriteTransaction(final String firstName, final String lastName) { LOG.info("validating write transaction for group " + firstName + " " + lastName + "..."); try { _db.begin(); Identity fullname = new Identity("First", "Person"); LazyEmployee person; person = _db.load(LazyEmployee.class, fullname); person.setBirthday(JDO_UPDATED_DATE); Iterator<LazyAddress> itor = person.getAddress().iterator(); while (itor.hasNext()) { LazyAddress address = itor.next(); String newStreet = new String(Integer.toString(address.getId()) + JDO_UPDATED_STRING); address.setStreet(newStreet); } _db.commit(); } catch (Exception e) { LOG.error( "validWriteTransaction: exception while validating write for " + firstName + " " + lastName, e); return false; } return true; } /** * Delete all objects in test data set. * * @throws Exception */ private void deleteTestDataSet() throws Exception { LOG.info("deleting test data set..."); _db.begin(); Statement stmt = _db.getJdbcConnection().createStatement(); stmt.executeUpdate("DELETE FROM test89_pks_person"); stmt.executeUpdate("DELETE FROM test89_pks_employee"); stmt.executeUpdate("DELETE FROM test89_pks_payroll"); stmt.executeUpdate("DELETE FROM test89_pks_address"); stmt.executeUpdate("DELETE FROM test89_pks_contract"); stmt.executeUpdate("DELETE FROM test89_pks_category"); stmt.executeUpdate("DELETE FROM test89_pks_project"); _db.commit(); } }