blob: dcc158eec440c52928fb23ff0dd99e46afdfac58 [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation.
*
* 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.apache.jdo.tck.lifecycle;
import java.util.Iterator;
import javax.jdo.Extent;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import org.apache.jdo.tck.JDO_Test;
import org.apache.jdo.tck.pc.lifecycle.StateTransitionObj;
import org.apache.jdo.tck.util.BatchTestRunner;
/**
*<B>Title:</B> Test State Transitions
*<BR>
*<B>Keywords:</B> lifecycle
*<BR>
*<B>Assertion IDs:</B> A5.9-1 through A5.9-190
*<B>Assertion Description: </B>
All possible state transistions are being tested in this test.
*/
public class StateTransitions extends JDO_Test {
/** */
private static final String ASSERTION_FAILED =
"Assertions A5.9-1 through A5.9-190 (StateTransitions) failed: ";
/**
* The <code>main</code> is called when the class
* is directly executed from the command line.
* @param args The arguments passed to the program.
*/
public static void main(String[] args) {
BatchTestRunner.run(StateTransitions.class);
}
private Transaction transaction;
private int scenario;
private int operation;
private int current_state;
private int expected_state;
private int new_state;
/**
* Operations that cause state changes
*/
private static final int MAKEPERSISTENT = 0;
private static final int DELETEPERSISTENT = 1;
private static final int MAKETRANSACTIONAL = 2;
private static final int MAKENONTRANSACTIONAL = 3;
private static final int MAKETRANSIENT = 4;
private static final int COMMITNORETAINVALUES = 5;
private static final int COMMITRETAINVALUES = 6;
private static final int ROLLBACKNORESTOREVALUES = 7;
private static final int ROLLBACKRESTOREVALUES = 8;
private static final int REFRESHDATASTORE = 9;
private static final int REFRESHOPTIMISTIC = 10;
private static final int EVICT = 11;
private static final int READOUTSIDETX = 12;
private static final int READOPTIMISTIC = 13;
private static final int READDATASTORE = 14;
private static final int WRITEOUTSIDETX = 15;
private static final int WRITEINSIDETX = 16;
private static final int RETRIEVEOUTSIDETX = 17;
private static final int RETRIEVEINSIDETX = 18;
private static final String[] operations = {
"makePersistent",
"deletePersistent",
"makeTransactional",
"makeNontransactional",
"makeTransient",
"commit, retainValues=false",
"commit, retainValues=true",
"rollback, restoreValues=false",
"rollback, restoreValues=true",
"refresh with active datastore tx",
"refresh with active optimistic tx",
"evict",
"read field outside tx",
"read field with active optimistic tx",
"read field with active datastore tx",
"write field outside tx",
"write field with active tx",
"retrieve outside tx",
"retrieve with active tx"
};
private static final int NUM_OPERATIONS = 19;
private static final boolean[] closes_transaction =
{ false, false, false, false, false, true, true, true, true, false,
false, false, false, false, false, false, false, false, false };
/**
* Illegal state transitions
*/
private static final int UNCHANGED = -1;
private static final int ERROR = -2;
private static final int IMPOSSIBLE = -3;
private static final int NOT_APPLICABLE = -4;
/**
* State transitions
*/
public static final int[][] transitions = { // [operation] [current state] = new state
// makePersistent
{ PERSISTENT_NEW, UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_NEW,
PERSISTENT_NEW, UNCHANGED, UNCHANGED, UNCHANGED},
// deletePersistent
{ ERROR, PERSISTENT_NEW_DELETED, PERSISTENT_DELETED, PERSISTENT_DELETED,
PERSISTENT_DELETED, ERROR, ERROR, UNCHANGED, UNCHANGED, PERSISTENT_DELETED},
// makeTransactional
{ TRANSIENT_CLEAN, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_CLEAN,
UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_CLEAN},
// makeNontransactional
{ ERROR, ERROR, PERSISTENT_NONTRANSACTIONAL, ERROR, UNCHANGED,
TRANSIENT, ERROR, ERROR, ERROR, UNCHANGED},
// makeTransient
{ UNCHANGED, ERROR, TRANSIENT, ERROR, TRANSIENT,
UNCHANGED, UNCHANGED, ERROR, ERROR, TRANSIENT},
// commit, retainValues = false
{ UNCHANGED, HOLLOW, HOLLOW, HOLLOW, UNCHANGED, UNCHANGED,
TRANSIENT_CLEAN, TRANSIENT, TRANSIENT, UNCHANGED},
// commit, retainValues = true
{ UNCHANGED, PERSISTENT_NONTRANSACTIONAL, PERSISTENT_NONTRANSACTIONAL,
PERSISTENT_NONTRANSACTIONAL, UNCHANGED, UNCHANGED, TRANSIENT_CLEAN,
TRANSIENT, TRANSIENT, UNCHANGED},
// rollback, restoreValues = false
{ UNCHANGED, TRANSIENT, HOLLOW, HOLLOW, UNCHANGED, UNCHANGED,
TRANSIENT_CLEAN, TRANSIENT, HOLLOW, UNCHANGED},
// rollback, restoreValues = true
{ UNCHANGED, TRANSIENT, PERSISTENT_NONTRANSACTIONAL, PERSISTENT_NONTRANSACTIONAL,
UNCHANGED, UNCHANGED, TRANSIENT_CLEAN, TRANSIENT, PERSISTENT_NONTRANSACTIONAL, UNCHANGED},
// refresh with active datastore transaction
{ UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_CLEAN, UNCHANGED,
UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED},
// refresh with active optimistic transaction
{ UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_NONTRANSACTIONAL, UNCHANGED,
UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED},
// evict
{ NOT_APPLICABLE, UNCHANGED, HOLLOW, UNCHANGED, UNCHANGED,
UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, HOLLOW},
// read field outside transaction
{ UNCHANGED, IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE, PERSISTENT_NONTRANSACTIONAL,
UNCHANGED, IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE, UNCHANGED},
// read field with active optimistic transaction
{ UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_NONTRANSACTIONAL,
UNCHANGED, UNCHANGED, ERROR, ERROR, UNCHANGED},
// read field with active datastore transaction
{ UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_CLEAN,
UNCHANGED, UNCHANGED, ERROR, ERROR, PERSISTENT_CLEAN},
// write field outside transaction
{ UNCHANGED, IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE, PERSISTENT_NONTRANSACTIONAL,
UNCHANGED, IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE, UNCHANGED},
// write field with active transaction
{ UNCHANGED, UNCHANGED, PERSISTENT_DIRTY, UNCHANGED, PERSISTENT_DIRTY,
TRANSIENT_DIRTY, UNCHANGED, ERROR, ERROR, PERSISTENT_DIRTY},
// retrieve outside transaction
{ UNCHANGED, IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE, PERSISTENT_NONTRANSACTIONAL,
UNCHANGED, IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE, UNCHANGED},
// retrieve with active transaction
{ UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_CLEAN,
UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, PERSISTENT_CLEAN}
};
private static final int DATASTORE_TX = 0;
private static final int OPTIMISTIC_TX = 1;
private static final int NO_TX = 2;
private static final String[] scenario_string = {
"datastore transaction", "optimistic transaction", "no transaction"
};
private static final boolean[][] applies_to_scenario = {
// Datastore Optimistic No tx
{ true, true, false }, // makePersistent
{ true, true, false }, // deletePersistent
{ true, true, false }, // makeTransactional
{ true, true, false }, // makeNontransactional
{ true, true, false }, // makeTransient
{ true, true, false }, // commit RetainValues = false
{ true, true, false }, // commit RetainValues = true
{ true, true, false }, // rollback RestoreValues = false
{ true, true, false }, // rollback RestoreValues = true
{ true, false, false }, // refresh with active datastore transaction
{ false, true, false }, // refresh with active optimistic transaction
{ true, true, false }, // evict
{ false, false, true }, // read field outside of a transaction
{ false, true, false }, // read field with active optimistic transaction
{ true, false, false }, // read field with active datastore transaction
{ false, false, true }, // write field or makeDirty outside of a transaction
{ true, true, false }, // write field or makeDirty with active transaction
{ false, true, true }, // retrieve outside of a transaction or with active optimistic transaction
{ true, false, false } // retrieve with active datastore transaction
};
public StateTransitions()
{
}
public void test() {
pm = getPM();
generatePersistentInstances();
scenario = DATASTORE_TX;
checkTransitions();
if( isOptimisticSupported() ){
scenario = OPTIMISTIC_TX;
checkTransitions();
}
scenario = NO_TX;
checkTransitions();
}
/** */
private void generatePersistentInstances()
{
if( doPersistentInstancesExist() ) return;
int i;
Transaction t = pm.currentTransaction();
t.begin();
for( i = 0; i < 50; ++i ){
StateTransitionObj sto = new StateTransitionObj(i);
sto.writeField(i);
pm.makePersistent(sto);
}
t.commit();
if( !doPersistentInstancesExist() )
if (debug)
logger.debug("StateTransitions unable to create instances of StateTransitionsObj");
}
/** */
private boolean doPersistentInstancesExist()
{
boolean ret;
Transaction t = pm.currentTransaction();
t.begin();
Extent e = pm.getExtent(StateTransitionObj.class, false);
Iterator iter = e.iterator();
ret = iter.hasNext();
t.rollback();
return ret;
}
/** */
void checkTransitions()
{
for( operation = 0; operation < NUM_OPERATIONS; ++operation ){
// rule out situations that do not apply
if( ! applies_to_scenario[operation][scenario] ) continue;
if( operation == READOUTSIDETX && !isNontransactionalReadSupported() ) continue;
if( operation == WRITEOUTSIDETX && !isNontransactionalWriteSupported() ) continue;
if( operation == COMMITRETAINVALUES && !isRetainValuesSupported() ) continue;
if( operation == MAKENONTRANSACTIONAL &&
!(isNontransactionalReadSupported() || isNontransactionalWriteSupported()) )
continue;
for( current_state = 0; current_state < NUM_STATES; ++current_state){
if( scenario == OPTIMISTIC_TX && current_state == PERSISTENT_CLEAN ) continue;
if( (current_state == TRANSIENT_CLEAN || current_state == TRANSIENT_DIRTY) &&
!isTransientTransactionalSupported() )
continue; // this state is not supported by implementation
if( current_state == PERSISTENT_NONTRANSACTIONAL &&
!(isNontransactionalReadSupported() || isNontransactionalWriteSupported()) )
continue; // this state is not supported by implementation
expected_state = transitions[operation][current_state];
if( expected_state == IMPOSSIBLE ) continue;
if( expected_state == NOT_APPLICABLE ) continue;
if( expected_state == UNCHANGED ) expected_state = current_state;
try {
transaction = pm.currentTransaction();
if( transaction.isActive()){
if (debug)
logger.debug("Transaction is active (but should not be), rolling back");
transaction.rollback();
}
if( scenario != NO_TX ){
if( operation == COMMITNORETAINVALUES )
transaction.setRetainValues(false);
if( operation == COMMITRETAINVALUES )
transaction.setRetainValues(true);
if( operation == ROLLBACKNORESTOREVALUES )
transaction.setRestoreValues(false);
if( operation == ROLLBACKRESTOREVALUES )
transaction.setRestoreValues(true);
transaction.setOptimistic(scenario == OPTIMISTIC_TX);
transaction.begin();
if( !transaction.isActive() )
if (debug)
logger.debug("StateTransitions: Transaction should be active, but it is not");
}
StateTransitionObj obj = getInstanceInState(current_state);
if( obj == null ){ // could not get object in state
if( transaction.isActive() ) transaction.rollback();
continue;
}
// Apply operation, catching possible exception
Exception e = null;
try {
applyOperation(operation, obj);
} catch( Exception excep ){
if( excep instanceof javax.jdo.JDOUserException ){
e = excep;
} else {
printSituation();
fail(ASSERTION_FAILED,
"StateTransitions: Unexpected exception:" + excep);
continue;
}
}
// Get new state, verify correct transition and exceptions occurred
new_state = currentState(obj);
if( expected_state == ERROR ){
if( e == null ){
printSituation();
fail(ASSERTION_FAILED,
"StateTransitions: JDOUserException should have been thrown");
} else {
if( new_state != current_state ){
printSituation();
fail(ASSERTION_FAILED,
"StateTransitions: " +
" JDOUserException properly thrown, but instance should remain in current state," +
"instance changed state to " + states[new_state]);
}
}
}
if( expected_state >= 0 && new_state != expected_state &&
!((new_state == HOLLOW && expected_state == PERSISTENT_NONTRANSACTIONAL) ||
(new_state == PERSISTENT_NONTRANSACTIONAL && expected_state == HOLLOW)) ) {
// status interrogation gives same values for PERSISTENT_NONTRANSACTIONAL and HOLLOW
printSituation();
fail(ASSERTION_FAILED,
"StateTransitions: Invalid state transition to " +
states[new_state] + ", new state should be " +
states[expected_state]);
}
if( transaction.isActive() ) transaction.rollback();
}
catch(Exception unexpected_exception) {
printSituation();
if (transaction.isActive())
transaction.rollback();
fail(ASSERTION_FAILED,
"Unexpected exception caught in StateTransitions " + unexpected_exception);
}
}
}
}
/** */
void printSituation()
{
if (debug) {
logger.debug(" (" + scenario_string[scenario] +
", initial state=" + states[current_state] +
", operation=" + operations[operation] + ")");
}
}
/** */
void applyOperation(int operation, StateTransitionObj stobj)
{
StateTransitionObj obj = (StateTransitionObj) stobj;
switch( operation ){
case MAKEPERSISTENT:
{
pm.makePersistent(obj);
break;
}
case DELETEPERSISTENT:
{
pm.deletePersistent(obj);
break;
}
case MAKETRANSACTIONAL:
{
pm.makeTransactional(obj);
break;
}
case MAKENONTRANSACTIONAL:
{
pm.makeNontransactional(obj);
break;
}
case MAKETRANSIENT:
{
pm.makeTransient(obj);
break;
}
case COMMITNORETAINVALUES:
{
pm.currentTransaction().commit();
break;
}
case COMMITRETAINVALUES:
{
pm.currentTransaction().commit();
break;
}
case ROLLBACKNORESTOREVALUES:
{
pm.currentTransaction().rollback();
break;
}
case ROLLBACKRESTOREVALUES:
{
pm.currentTransaction().rollback();
break;
}
case REFRESHDATASTORE:
{
pm.refresh(obj);
break;
}
case REFRESHOPTIMISTIC:
{
pm.refresh(obj);
break;
}
case EVICT:
{
pm.evict(obj);
break;
}
case READOUTSIDETX:
{
int val = obj.readField();
break;
}
case READOPTIMISTIC:
{
int val = obj.readField();
break;
}
case READDATASTORE:
{
int val = obj.readField();
break;
}
case WRITEOUTSIDETX:
{
obj.writeField(42);
break;
}
case WRITEINSIDETX:
{
obj.writeField(42);
break;
}
case RETRIEVEOUTSIDETX:
{
pm.retrieve(obj);
break;
}
case RETRIEVEINSIDETX:
{
pm.retrieve(obj);
break;
}
default:
{
fail(ASSERTION_FAILED,
"StateTransitions internal error, illegal operation " + operation);
}
}
}
/**
* Get an instance in the specified state.
*/
private StateTransitionObj getInstanceInState(int state)
{
switch(state) {
case TRANSIENT:
return getTransientInstance();
case PERSISTENT_NEW:
return getPersistentNewInstance();
case PERSISTENT_CLEAN:
return getPersistentCleanInstance();
case PERSISTENT_DIRTY:
return getPersistentDirtyInstance();
case HOLLOW:
return getHollowInstance();
case TRANSIENT_CLEAN:
return getTransientCleanInstance();
case TRANSIENT_DIRTY:
return getTransientDirtyInstance();
case PERSISTENT_NEW_DELETED:
return getPersistentNewDeletedInstance();
case PERSISTENT_DELETED:
return getPersistentDeletedInstance();
case PERSISTENT_NONTRANSACTIONAL:
return getPersistentNontransactionalInstance();
default:
{
return null;
}
}
}
/** */
private StateTransitionObj getTransientInstance()
{
StateTransitionObj obj = new StateTransitionObj(23);
int curr = currentState(obj);
if( curr != TRANSIENT ) {
if (debug) {
logger.debug("StateTransitions: Unable to create transient instance, state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
private StateTransitionObj getPersistentNewInstance()
{
StateTransitionObj obj = getTransientInstance();
if( obj == null ) return null;
pm.makePersistent(obj); // should transition to persistent-new
int curr = currentState(obj);
if( curr != PERSISTENT_NEW ) {
if (debug) {
logger.debug("StateTransitions: Unable to create persistent-new instance" +
" from transient instance via makePersistent(), state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentCleanInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
StateTransitionObj sto = (StateTransitionObj) obj;
int val = sto.readField();
int curr = currentState(sto);
if( curr != PERSISTENT_CLEAN ) {
if (debug) {
logger.debug("StateTransition: Unable to create persistent-clean instance" +
" from a hollow instance by reading a field, state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentDirtyInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
StateTransitionObj pcobj = (StateTransitionObj) obj;
pcobj.writeField(23);
int curr = currentState(obj);
if( curr != PERSISTENT_DIRTY ) {
if (debug) {
logger.debug("StateTransition: Unable to create persistent-dirty instance" +
" from a hollow instance by writing a field, state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getHollowInstance()
{
Extent extent = pm.getExtent(StateTransitionObj.class, false);
Iterator iter = extent.iterator();
if( !iter.hasNext() ){
if (debug)
logger.debug("Extent for StateTransitionObj should not be empty");
return null;
}
StateTransitionObj obj = (StateTransitionObj) iter.next();
transaction.setRetainValues(false);
if ( !transaction.isActive() )
transaction.begin();
if( !transaction.isActive() )
if (debug)
logger.debug("getHollowInstance: Transaction should be active, but it is not");
transaction.commit(); // This should put the instance in the HOLLOW state
if( scenario != NO_TX ){
if( operation == COMMITNORETAINVALUES )
transaction.setRetainValues(false);
if( operation == COMMITRETAINVALUES )
transaction.setRetainValues(true);
if( operation == ROLLBACKNORESTOREVALUES )
transaction.setRestoreValues(false);
if( operation == ROLLBACKRESTOREVALUES )
transaction.setRestoreValues(true);
transaction.setOptimistic(scenario == OPTIMISTIC_TX);
transaction.begin();
if( !transaction.isActive() )
if (debug)
logger.debug("getHollowInstance: Transaction should be active, but it is not");
}
int curr = currentState(obj);
if( curr != HOLLOW && curr != PERSISTENT_NONTRANSACTIONAL ){
if (debug) {
logger.debug("StateTransition: Attempt to get hollow instance via accessing extent failed, state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getTransientCleanInstance()
{
StateTransitionObj obj = getTransientInstance();
if( obj == null ) return null;
pm.makeTransactional(obj);
int curr = currentState(obj);
if( curr != TRANSIENT_CLEAN ) {
if (debug) {
logger.debug("StateTransition: Unable to create transient-clean instance" +
" from a transient instance via makeTransactional(), state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getTransientDirtyInstance()
{
StateTransitionObj obj = getTransientCleanInstance();
if( obj == null ) return null;
StateTransitionObj pcobj = (StateTransitionObj) obj;
pcobj.writeField(23);
int curr = currentState(obj);
if( curr != TRANSIENT_DIRTY ) {
if (debug) {
logger.debug("StateTransition: Unable to create transient-dirty instance" +
" from a transient-clean instance via modifying a field, state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentNewDeletedInstance()
{
StateTransitionObj obj = getPersistentNewInstance();
if( obj == null ) return null;
pm.deletePersistent(obj); // should transition to persistent-new-deleted
int curr = currentState(obj);
if( curr != PERSISTENT_NEW_DELETED) {
if (debug) {
logger.debug("StateTransition: Unable to create persistent-new-deleted instance" +
" from a persistent-new instance via deletePersistent, state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentDeletedInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
pm.deletePersistent(obj);
int curr = currentState(obj);
if( curr != PERSISTENT_DELETED ) {
if (debug) {
logger.debug("StateTransition: Unable to create persistent-deleted instance" +
" from a persistent instance via deletePersistent(), state is " +
states[curr]);
}
printSituation();
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentNontransactionalInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
pm.makeNontransactional(obj);
int curr = currentState(obj);
if( curr != PERSISTENT_NONTRANSACTIONAL && curr != HOLLOW ) {
if (debug) {
logger.debug("StateTransition: Unable to create persistent-nontransactional instance" +
" from a persistent-clean instance via makeNontransactional(), state is " +
states[curr]);
}
printSituation();
return null;
}
return null;
}
}