| /* |
| * 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) { |
| } |
| } |
| } |