/******************************************************************************* | |
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation from Oracle TopLink | |
******************************************************************************/ | |
package org.eclipse.persistence.testing.tests.flashback; | |
import java.util.*; | |
import java.sql.*; | |
import org.eclipse.persistence.sessions.*; | |
import org.eclipse.persistence.platform.database.OraclePlatform; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.history.*; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.testing.tests.expressions.*; | |
import org.eclipse.persistence.testing.tests.clientserver.*; | |
import org.eclipse.persistence.testing.tests.sessionbroker.*; | |
import org.eclipse.persistence.testing.framework.*; | |
import org.eclipse.persistence.testing.models.employee.domain.*; | |
import org.eclipse.persistence.testing.models.employee.relational.EmployeeSystem; | |
import org.eclipse.persistence.testing.tests.employee.EmployeeBasicTestModel; | |
/** | |
* Tests Oracle Flashback support. | |
* Note this model is also subclassed for generic history support | |
* and support both historical models. | |
*/ | |
public class FlashbackTestModel extends TestModel { | |
protected Number systemChangeNumber; | |
public static java.sql.Timestamp timestamp; | |
protected AsOfClause asOfClause; | |
public final static boolean QUICK_SETUP = true; | |
public final static boolean STANDARD_SETUP = true; | |
public FlashbackTestModel() { | |
setDescription("Tests the new flashback query tests. Includes running the expression test suite as of a past time."); | |
} | |
public Expression adaptExpression(Expression expression) { | |
return expression.asOf(getAsOfClause()); | |
} | |
public void adaptQuery(ObjectLevelReadQuery query) { | |
if (getSystemChangeNumber() != null) { | |
query.setAsOfClause(getAsOfClause()); | |
} | |
} | |
public org.eclipse.persistence.sessions.Session adaptSession(Session session) { | |
return session.acquireHistoricalSession(getAsOfClause()); | |
} | |
public void addTests() { | |
// Add ExpressionTestSuite... | |
// | |
//assert getSCNValue() != null; | |
ExpressionTestSuite expressionTestSuite = new ExpressionTestSuite(); | |
//expressionTestSuite.setExecutor(getExecutor()); | |
//expressionTestSuite.addTests(); | |
for (Iterator iter = expressionTestSuite.getTests().iterator(); iter.hasNext();) { | |
TestEntity baseTest = (TestEntity)iter.next(); | |
if (baseTest instanceof ReadAllExpressionTest) { | |
ReadAllExpressionTest test = (ReadAllExpressionTest)baseTest; | |
if (test.getExpression() == null) { | |
//System.out.println("This test does not have an expression yet: " + test.getName()); | |
iter.remove(); | |
} else { | |
if (test.getReferenceClass().getPackage() != Employee.class.getPackage()) { | |
iter.remove(); | |
} else { | |
// For these test setting it on the builder. | |
test.getQuery(true).dontMaintainCache(); | |
test.getQuery().cascadeAllParts(); | |
test.getExpression().getBuilder().asOf(getAsOfClause()); | |
} | |
} | |
} else { | |
iter.remove(); | |
} | |
} | |
addTest(expressionTestSuite); | |
ExpressionSubSelectTestSuite subSelectTestSuite = new ExpressionSubSelectTestSuite(); | |
subSelectTestSuite.setExecutor(getExecutor()); | |
for (Iterator iter = subSelectTestSuite.getTests().iterator(); iter.hasNext();) { | |
TestEntity baseTest = (TestEntity)iter.next(); | |
if (baseTest instanceof ReadAllExpressionTest) { | |
ReadAllExpressionTest test = (ReadAllExpressionTest)baseTest; | |
if (test.getExpression() == null) { | |
//System.out.println("This test does not have an expression yet: " + test.getName()); | |
iter.remove(); | |
} else { | |
if ((test.getReferenceClass().getPackage() != Employee.class.getPackage()) || test.getName().equals("SubSelectCustomSQLTest")) { | |
iter.remove(); | |
} else { | |
// For these must set on the entire expression, as multiple builders. | |
test.getQuery(true).dontMaintainCache(); | |
test.getQuery().cascadeAllParts(); | |
test.getExpression().asOf(getAsOfClause()); | |
} | |
} | |
} else { | |
iter.remove(); | |
} | |
} | |
addTest(subSelectTestSuite); | |
/**ExpressionOuterJoinTestSuite outerJoinTestSuite = new ExpressionOuterJoinTestSuite(); | |
outerJoinTestSuite.setExecutor(getExecutor()); | |
outerJoinTestSuite.addTests(); | |
for (Iterator iter = outerJoinTestSuite.getTests().iterator(); iter.hasNext();) { | |
TestEntity baseTest = (TestEntity)iter.next(); | |
if (baseTest instanceof ReadAllExpressionTest) { | |
ReadAllExpressionTest test = (ReadAllExpressionTest)baseTest; | |
if (test.getExpression() == null) { | |
//System.out.println("This test does not have an expression yet: " + test.getName()); | |
iter.remove(); | |
} else { | |
if (test.getReferenceClass().getPackage() != Employee.class.getPackage()) { | |
iter.remove(); | |
} else { | |
// For these test setting it on the builder. | |
test.getQuery(true).dontMaintainCache(); | |
test.getExpression().getBuilder().asOf(getAsOfClause()); | |
} | |
} | |
} else { | |
iter.remove(); | |
} | |
} | |
addTest(outerJoinTestSuite); | |
*/ | |
// Run ReadObjectTestSuite using HistoricalSession, to test relationship | |
// reading. | |
TestSuite suite = EmployeeBasicTestModel.getReadObjectTestSuite(); | |
suite.setDescription("Tests object relationships in a HistoricalSession."); | |
Vector suiteTests = suite.getTests(); | |
Vector newTests = new Vector(suiteTests.size()); | |
int numTests = suiteTests.size(); | |
HistoricalSessionTest hsTest; | |
AutoVerifyTestCase wrappedTest; | |
for (int i = 0; i < numTests; i++) { | |
wrappedTest = (AutoVerifyTestCase)suiteTests.get(i); | |
if (!(wrappedTest.getName().indexOf("Call") > 0)) { | |
hsTest = new HistoricalSessionTest(wrappedTest, getAsOfClause()); | |
newTests.add(hsTest); | |
} | |
} | |
suiteTests.clear(); | |
suiteTests.addAll(newTests); | |
addTest(suite); | |
// Run ReadObjectTestSuite using HistoricalSession acquired from either | |
// a ClientSession or a ClientSessionBroker. | |
suite = EmployeeBasicTestModel.getReadObjectTestSuite(); | |
suite.setName("AnySessionsTestSuite"); | |
suite.setDescription("Tests flashback on other session setups."); | |
suiteTests = suite.getTests(); | |
newTests = new Vector(suiteTests.size()); | |
numTests = suiteTests.size(); | |
ClientSessionTestAdapter csTest; | |
ClientSessionBrokerTestAdapter csbTest; | |
boolean oneForYou = false; | |
for (int i = 0; i < numTests; i++) { | |
wrappedTest = (AutoVerifyTestCase)suiteTests.get(i); | |
if (!(wrappedTest.getName().indexOf("Call") > 0)) { | |
hsTest = new HistoricalSessionTest(wrappedTest, getAsOfClause()); | |
if (oneForYou) { | |
csTest = new ClientSessionTestAdapter(hsTest); | |
newTests.add(csTest); | |
} else { | |
csbTest = new ClientSessionBrokerTestAdapter(hsTest); | |
newTests.add(csbTest); | |
} | |
oneForYou = !oneForYou; | |
} | |
} | |
suiteTests.clear(); | |
suiteTests.addAll(newTests); | |
addTest(suite); | |
// Run read all test suite as of a past time using a HistoricalSession. | |
// These tests came up with nothing useful at the time. | |
suite = EmployeeBasicTestModel.getReadAllTestSuite(); | |
suiteTests = suite.getTests(); | |
newTests.clear(); | |
numTests = suiteTests.size(); | |
for (int i = 0; i < numTests; i++) { | |
wrappedTest = (AutoVerifyTestCase)suiteTests.get(i); | |
if (!(wrappedTest.getName().indexOf("Call") > 0)) { | |
hsTest = new HistoricalSessionTest(wrappedTest, getAsOfClause()); | |
newTests.add(hsTest); | |
} | |
} | |
suiteTests.clear(); | |
suiteTests.addAll(newTests); | |
addTest(suite); | |
// Now add some individual tests in here. | |
TestSuite flashbackSuite = new FlashbackUnitTestSuite(); | |
flashbackSuite.setExecutor(getExecutor()); | |
addTest(flashbackSuite); | |
} | |
public void buildAsOfClause() { | |
if (getSystemChangeNumber() != null) { | |
asOfClause = new AsOfSCNClause(getSystemChangeNumber()); | |
} else { | |
asOfClause = new AsOfClause(getTimestamp()); | |
} | |
} | |
/** | |
* Find the system change number that reflects the initial fully | |
* populated state of the database. | |
* Assumes that when called the database is in its start state. | |
*/ | |
protected void calculateSystemChangeNumber() throws Exception { | |
OraclePlatform platform = (OraclePlatform)getSession().getPlatform(); | |
ValueReadQuery scnQuery = platform.getSystemChangeNumberQuery(); | |
ReadAllQuery query = new ReadAllQuery(Employee.class); | |
query.dontMaintainCache(); | |
int safetyCount = 1400; | |
boolean validSCN = false; | |
while ((safetyCount-- > 0) && !validSCN) { | |
this.systemChangeNumber = (Number)getSession().executeQuery(scnQuery); | |
//clonedQuery = (ReadAllQuery)query.clone(); | |
//query.setSelectionCriteria(query.getExpressionBuilder().get("salary").greaterThan(safetyCount)); | |
query.setAsOfClause(new AsOfSCNClause(systemChangeNumber)); | |
try { | |
Vector result = (Vector)getSession().executeQuery(query); | |
validSCN = true; | |
} catch (Exception e) { | |
// keep going... | |
Thread.sleep(1000 * 30); | |
} | |
} | |
} | |
/** | |
* By caching the result of this call, it is insured that | |
* a new timestamp is not calculated after rows are deleted. | |
* Furthermore, an asof time that maps to a desired snapshot of | |
* the database must be had. | |
*/ | |
public void calculateTimestamp() { | |
if (getTimestamp() == null) { | |
OraclePlatform platform = (OraclePlatform)getSession().getPlatform(); | |
ValueReadQuery timestampQuery = platform.getTimestampQuery(); | |
ReadAllQuery query = new ReadAllQuery(Employee.class); | |
query.setSelectionCriteria(query.getExpressionBuilder().get("firstName").notEqual("Eun Kyung")); | |
ReadAllQuery clonedQuery = null; | |
int safetyCount = 1400; | |
while ((safetyCount-- > 0) && (getTimestamp() == null)) { | |
java.util.Date timestamp = (java.util.Date)getSession().executeQuery(timestampQuery); | |
clonedQuery = query; | |
clonedQuery.setAsOfClause(new AsOfClause(timestamp)); | |
Vector result = (Vector)getSession().executeQuery(clonedQuery); | |
if (result.size() == 12) { | |
setTimestamp((java.sql.Timestamp)timestamp); | |
} else { | |
try { | |
Thread.sleep(10000); | |
} catch (InterruptedException e) { | |
throw new TestErrorException("Unable to wait for valid timstamp.", e); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* This idea of calculateTimestamp is to get a snapshot at which time | |
* the database is fully populated. | |
* Tries going further and further back in time to find one. | |
* Note if go back too far, or before the last time a table was altered, an | |
* exception will result. | |
*/ | |
@SuppressWarnings("deprecation") | |
public void calculateTimestampHopefully() { | |
if (getTimestamp() != null) { | |
return; | |
} | |
OraclePlatform platform = (OraclePlatform)getSession().getPlatform(); | |
ValueReadQuery timestampQuery = platform.getTimestampQuery(); | |
ReadObjectQuery query = new ReadObjectQuery(Employee.class); | |
query.dontMaintainCache(); | |
query.addPartialAttribute("id"); | |
ReadObjectQuery asOfQuery = null; | |
try { | |
long asOfTime = ((Timestamp)getSession().executeQuery(timestampQuery)).getTime(); | |
long timeToGoBack = 0; | |
while (getTimestamp() == null) { | |
asOfQuery = (ReadObjectQuery)query.clone(); | |
asOfQuery.setAsOfClause(new AsOfClause(new java.sql.Timestamp(asOfTime - timeToGoBack))); | |
if (getSession().executeQuery(asOfQuery) != null) { | |
setTimestamp(new Timestamp(asOfTime - timeToGoBack)); | |
} else { | |
// go back 10 minutes each time. | |
timeToGoBack += ((timeToGoBack == 0) ? (1000 * 60) : timeToGoBack); | |
} | |
} | |
} catch (Exception e) { | |
System.out.println("Went too far back, so can't tell this."); | |
} | |
} | |
private void configure() throws Exception { | |
if (!getSession().getPlatform().isOracle()) { | |
throw new TestWarningException("Flashback is only supported on Oracle 9R2 or later databases."); | |
} | |
TestSystem system = new EmployeeSystem(); | |
if (STANDARD_SETUP) { | |
system.run(getSession()); | |
// This delay here is nasty but inevitable. Every time the tables get | |
// recreated (i.e. by every test model) flashback is disabled for | |
// 5 minutes. | |
// Now sleeps for 5 seconds only - seem to be ok. | |
System.out.println("Starting to sleep for 5 seconds."); | |
Thread.sleep(1000 * 5); | |
System.out.println("Have stopped sleeping for 5 seconds."); | |
calculateSystemChangeNumber(); | |
//calculateTimestamp(); | |
buildAsOfClause(); | |
depopulate(); | |
return; | |
} | |
// Only in the non standard case, try to avoid the five minute setup hit. | |
// Only useable when run individually at a separate time. | |
boolean mustAddTables = false; | |
Vector existingEmployees = null; | |
// First check that descriptors exist? | |
try { | |
Object result = getSession().readObject(Employee.class); | |
} catch (QueryException qe) { | |
system.addDescriptors((DatabaseSession)getSession()); | |
} catch (Exception goNextStep) { | |
} | |
// Now do tables exist on database? | |
try { | |
existingEmployees = getSession().readAllObjects(Employee.class); | |
} catch (DatabaseException de) { | |
mustAddTables = true; | |
} | |
if (mustAddTables || !QUICK_SETUP) { | |
if (mustAddTables) { | |
system.createTables((DatabaseSession)getSession()); | |
} | |
system.populate((DatabaseSession)getSession()); | |
// Must init identity maps as contains same objects as in | |
// population manager! | |
getSession().getIdentityMapAccessor().initializeAllIdentityMaps(); | |
// Wait for a snapshot that shows the newly added items. | |
// This is going to take five minutes. | |
calculateSystemChangeNumber(); | |
//calculateTimestamp(); | |
depopulate(); | |
} else { | |
depopulate(true); | |
system.populate((DatabaseSession)getSession()); | |
getSession().getIdentityMapAccessor().initializeAllIdentityMaps(); | |
calculateSystemChangeNumber(); | |
depopulate(); | |
//calculateTimestampHopefully(); | |
// Must at this time fill up the PopulationManager. | |
// This is pretty useless though, as they all have the | |
// wrong primary keys. | |
//EmployeePopulator populator = new EmployeePopulator(); | |
//populator.buildExamples(); | |
//if (existingEmployees != null) { | |
//depopulate(); | |
//} | |
} | |
buildAsOfClause(); | |
return; | |
} | |
public void depopulate() throws Exception { | |
depopulate(false); | |
} | |
public void depopulate(boolean fully) throws Exception { | |
try { | |
// Now here is the tricky part: corrupt all the objects | |
// that exist right now! Or at least some of them. | |
UnitOfWork uow = getSession().acquireUnitOfWork(); | |
Vector employees = uow.readAllObjects(Employee.class); | |
Vector projects = uow.readAllObjects(LargeProject.class); | |
Vector smallProjects = uow.readAllObjects(SmallProject.class); | |
Vector addresses = uow.readAllObjects(Address.class); | |
for (Enumeration enumtr = employees.elements(); enumtr.hasMoreElements();) { | |
Employee emp = (Employee)enumtr.nextElement(); | |
//emp.setAddress(null); | |
emp.setProjects(null); | |
emp.setManager(null); | |
emp.setManagedEmployees(new Vector()); | |
emp.setAddress(null); | |
} | |
for (Enumeration enumtr = projects.elements(); enumtr.hasMoreElements();) { | |
LargeProject project = (LargeProject)enumtr.nextElement(); | |
project.setTeamLeader(null); | |
} | |
//uow.commit(); | |
//getSession().getIdentityMapAccessor().initializeAllIdentityMaps(); | |
//uow = getSession().acquireUnitOfWork(); | |
//employees = (Vector)uow.readAllObjects(Employee.class); | |
uow.deleteAllObjects(employees); | |
if (fully) { | |
uow.deleteAllObjects(projects); | |
uow.deleteAllObjects(smallProjects); | |
uow.deleteAllObjects(addresses); | |
} | |
uow.commit(); | |
} catch (EclipseLinkException e) { | |
throw e; | |
} catch (Exception e) { | |
System.out.println("Could not depopulate table."); | |
System.out.println("Exception:"+e); | |
e.printStackTrace(); | |
} | |
} | |
public Number getSystemChangeNumber() { | |
return systemChangeNumber; | |
} | |
public AsOfClause getAsOfClause() { | |
return asOfClause; | |
} | |
public static java.sql.Timestamp getTimestamp() { | |
return timestamp; | |
} | |
public static long getTimestampValue() { | |
return getTimestamp().getTime(); | |
} | |
public void setAsOfClause(AsOfClause asOfClause) { | |
this.asOfClause = asOfClause; | |
} | |
public static void setTimestamp(java.sql.Timestamp newTimestamp) { | |
timestamp = newTimestamp; | |
} | |
/** | |
* Assume setup() is called prior to addTests. This seems bizarre | |
* but is the way it works. | |
*/ | |
public void setup() { | |
// Must do configuration here... | |
if (getTimestamp() != null) { | |
return; | |
} | |
try { | |
configure(); | |
} catch (EclipseLinkException te) { | |
throw te; | |
} catch (Exception ignore) { | |
ignore.printStackTrace(); | |
} | |
} | |
public void reset() { | |
if (getTimestamp() == null) { | |
return; | |
} | |
try { | |
if (QUICK_SETUP) { | |
TestSystem system = new EmployeeSystem(); | |
system.populate((DatabaseSession)getSession()); | |
getSession().getIdentityMapAccessor().initializeAllIdentityMaps(); | |
} | |
} catch (Exception ignore) { | |
} | |
} | |
} |