blob: 2eda0391683a42e2bed4f701c003f0d25b7b26b5 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// 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());
}
@Override
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.
*/
@Override
public void setup() {
// Must do configuration here...
if (getTimestamp() != null) {
return;
}
try {
configure();
} catch (EclipseLinkException te) {
throw te;
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
@Override
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) {
}
}
}