blob: 015565c202cd66e006fe83a4d3d6306dfe7198e4 [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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import javax.jdo.Extent;
import javax.jdo.JDOFatalException;
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 StateTransitionsReturnedObjects extends JDO_Test {
/** */
private static final String ASSERTION_FAILED =
"Assertions A5.9-1 through A5.5.8 (serialization, detachment, attachment) 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(StateTransitionsReturnedObjects.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 DETACHCOPYOUTSIDETX = 1;
private static final int DETACHCOPYINSIDETX = 2;
private static final int SERIALIZEOUTSIDETX = 3;
private static final int SERIALIZEINSIDETX = 4;
private static final int NUM_OPERATIONS = 5;
private static final String[] operations = {
"makePersistent",
"detachCopy outside tx",
"detachCopy with active tx",
"serialize outside tx",
"serialize with active tx"
};
/**
* 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;
private static final int UNSPECIFIED = -5;
/**
* State transitions
*/
public static final int[][] statesOfReturnedObjects = { // [operation] [current state] = new state
// TRANSIENT, PERSISTENT_NEW, PERSISTENT_CLEAN,
// PERSISTENT_DIRTY, HOLLOW, TRANSIENT_CLEAN,
// TRANSIENT_DIRTY, PERSISTENT_NEW_DELETED, PERSISTENT_DELETED,
// PERSISTENT_NONTRANSACTIONAL, PERSISTENT_NONTRANSACTIONAL_DIRTY, DETACHED_CLEAN,
// DETACHED_DIRTY
// makePersistent
{ PERSISTENT_NEW, UNCHANGED, UNCHANGED,
UNCHANGED, UNCHANGED, PERSISTENT_NEW,
PERSISTENT_NEW, UNCHANGED, UNCHANGED,
UNCHANGED, UNCHANGED, UNSPECIFIED,
PERSISTENT_DIRTY},
// detachCopy outside tx
{ ERROR, IMPOSSIBLE, IMPOSSIBLE,
IMPOSSIBLE, UNSPECIFIED, IMPOSSIBLE,
IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE,
DETACHED_CLEAN, ERROR, UNCHANGED,
DETACHED_CLEAN},
// detachCopy with active tx
{ DETACHED_CLEAN, DETACHED_CLEAN, DETACHED_CLEAN,
DETACHED_CLEAN, DETACHED_CLEAN, DETACHED_CLEAN,
DETACHED_CLEAN, ERROR, ERROR,
DETACHED_CLEAN, ERROR, UNSPECIFIED,
UNSPECIFIED},
// serialize outside tx
{ UNCHANGED, IMPOSSIBLE, IMPOSSIBLE,
IMPOSSIBLE, DETACHED_CLEAN, IMPOSSIBLE,
IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE,
DETACHED_CLEAN, UNSPECIFIED, UNCHANGED,
UNCHANGED},
// serialize with active tx
{ UNCHANGED, DETACHED_CLEAN, DETACHED_CLEAN,
DETACHED_CLEAN, DETACHED_CLEAN, TRANSIENT,
TRANSIENT, UNSPECIFIED, UNSPECIFIED,
DETACHED_CLEAN, UNSPECIFIED, UNCHANGED,
UNCHANGED},
};
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
{ false, false, true }, // detachCopy outside tx
{ true, true, false }, // detachCopy with active tx
{ false, false, true }, // serialize outside tx
{ true, true, false } // serialize with active tx
};
/**
* @see JDO_Test#localSetUp()
*/
protected void localSetUp() {
pm = getPM();
addTearDownClass(StateTransitionObj.class);
generatePersistentInstances();
}
public void test() {
scenario = DATASTORE_TX;
checkTransitions();
if( isOptimisticSupported() ){
scenario = OPTIMISTIC_TX;
checkTransitions();
}
scenario = NO_TX;
checkTransitions();
failOnError();
}
/** */
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("StateTransitionsReturnedObjects 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;
}
/** */
public void prepareTransactionAndJDOSettings(Transaction transaction) {
if( scenario != NO_TX ) {
transaction.setNontransactionalRead(false);
transaction.setNontransactionalWrite(false);
pm.setDetachAllOnCommit(false);
transaction.setOptimistic(scenario == OPTIMISTIC_TX);
transaction.begin();
if( !transaction.isActive() )
if (debug)
logger.debug("StateTransitionsReturnedObjects: Transaction should be active, but it is not");
} else {
if( operation == DETACHCOPYOUTSIDETX ||
operation == SERIALIZEOUTSIDETX ) {
transaction.setNontransactionalRead(true);
}
if( current_state == PERSISTENT_NONTRANSACTIONAL_DIRTY) {
transaction.setNontransactionalWrite(true);
}
}
}
/** */
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 == DETACHCOPYOUTSIDETX ||
operation == SERIALIZEOUTSIDETX) &&
!isNontransactionalReadSupported() ) 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
if( current_state == PERSISTENT_NONTRANSACTIONAL_DIRTY &&
!isNontransactionalWriteSupported() )
continue;
expected_state = statesOfReturnedObjects[operation][current_state];
if( expected_state == IMPOSSIBLE ) continue;
if( expected_state == NOT_APPLICABLE ) continue;
if( expected_state == UNSPECIFIED ) 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();
}
prepareTransactionAndJDOSettings(transaction);
printSituation();
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;
Object returnedObject = null;
try {
returnedObject = applyOperation(operation, obj);
} catch( Exception excep ){
if( excep instanceof javax.jdo.JDOUserException ){
e = excep;
} else {
appendMessage(ASSERTION_FAILED + NL +
"StateTransitionsReturnedObjects: " +
scenario_string[scenario] +
"; current state " + states[current_state] + NL +
operations[operation] +
"; unexpected exception: " + excep);
continue;
}
}
if( expected_state == ERROR ){
if( e == null ){
appendMessage(ASSERTION_FAILED + NL +
"StateTransitionsReturnedObjects: " +
scenario_string[scenario] +
"; current state " + states[current_state] + NL +
operations[operation] +
"; JDOUserException should have been thrown");
} else {
int stateOfObj = currentState(obj);
if( stateOfObj != current_state ){
appendMessage(ASSERTION_FAILED + NL +
"StateTransitionsReturnedObjects: " +
scenario_string[scenario] +
"; current state " + states[current_state] + NL +
operations[operation] +
"; JDOUserException properly thrown, but instance should remain in current state," +
" instance changed state to " + states[stateOfObj]);
}
}
} else {
// Get new state, verify correct transition and exceptions occurred
new_state = currentState(returnedObject);
if( !compareStates(new_state, expected_state) ) {
appendMessage(ASSERTION_FAILED + NL +
"StateTransitionsReturnedObjects: " +
scenario_string[scenario] +
"; current state " + states[current_state] + NL +
operations[operation] +
" returned instance in invalid state " + states[new_state] +
"; expected state " + states[expected_state]);
}
}
if( transaction.isActive() ) transaction.rollback();
}
catch(Exception unexpected_exception) {
if (transaction.isActive())
transaction.rollback();
appendMessage(ASSERTION_FAILED + NL +
"StateTransitionsReturnedObjects: " +
scenario_string[scenario] +
"; current state " + states[current_state] + NL +
operations[operation] +
"; unexpected exception caught: " + unexpected_exception);
}
}
}
}
/** */
void printSituation()
{
if (debug) {
logger.debug(" (" + scenario_string[scenario] +
", initial state=" + states[current_state] +
", " + operations[operation] + ")");
}
}
/** */
Object applyOperation(int operation, StateTransitionObj stobj)
{
Object result = null;
StateTransitionObj obj = (StateTransitionObj) stobj;
switch( operation ){
case MAKEPERSISTENT:
result = pm.makePersistent(obj);
break;
case DETACHCOPYOUTSIDETX:
case DETACHCOPYINSIDETX:
result = pm.detachCopy(obj);
break;
case SERIALIZEOUTSIDETX:
case SERIALIZEINSIDETX:
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(obj);
ois = new ObjectInputStream(new ByteArrayInputStream(
byteArrayOutputStream.toByteArray()));
result = ois.readObject();
} catch (IOException e) {
throw new JDOFatalException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new JDOFatalException(e.getMessage(), e);
} finally {
try {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
throw new JDOFatalException(e.getMessage(), e);
}
}
break;
default:
appendMessage(ASSERTION_FAILED + NL +
"StateTransitionsReturnedObjects: " +
scenario_string[scenario] +
"; internal error, illegal operation: " + operation);
}
return result;
}
/**
* Get an instance in the specified state.
*/
public 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();
case PERSISTENT_NONTRANSACTIONAL_DIRTY:
return getPersistentNontransactionalDirtyInstance();
case DETACHED_CLEAN:
return getDetachedCleanInstance();
case DETACHED_DIRTY:
return getDetachedDirtyInstance();
default:
return null;
}
}
/** */
public StateTransitionObj getTransientInstance()
{
StateTransitionObj obj = new StateTransitionObj(23);
int curr = currentState(obj);
if( curr != TRANSIENT ) {
if (debug) {
logger.debug("StateTransitionsReturnedObjects: Unable to create transient instance, state is " +
states[curr]);
}
return null;
}
return obj;
}
/** */
public 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("StateTransitionsReturnedObjects: Unable to create persistent-new instance" +
" from transient instance via makePersistent(), state is " +
states[curr]);
}
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentCleanInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
StateTransitionObj sto = (StateTransitionObj) obj;
sto.readField();
int curr = currentState(sto);
if( curr != PERSISTENT_CLEAN ) {
if (debug) {
logger.debug("StateTransitionsReturnedObjects: Unable to create persistent-clean instance" +
" from a hollow instance by reading a field, state is " +
states[curr]);
}
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("StateTransitionsReturnedObjects: Unable to create persistent-dirty instance" +
" from a hollow instance by writing a field, state is " +
states[curr]);
}
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
prepareTransactionAndJDOSettings(transaction);
int curr = currentState(obj);
if( curr != HOLLOW && curr != PERSISTENT_NONTRANSACTIONAL ){
if (debug) {
logger.debug("StateTransitionsReturnedObjects: Attempt to get hollow instance via accessing extent failed, state is " +
states[curr]);
}
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("StateTransitionsReturnedObjects: Unable to create transient-clean instance" +
" from a transient instance via makeTransactional(), state is " +
states[curr]);
}
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("StateTransitionsReturnedObjects: Unable to create transient-dirty instance" +
" from a transient-clean instance via modifying a field, state is " +
states[curr]);
}
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("StateTransitionsReturnedObjects: Unable to create persistent-new-deleted instance" +
" from a persistent-new instance via deletePersistent, state is " +
states[curr]);
}
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("StateTransitionsReturnedObjects: Unable to create persistent-deleted instance" +
" from a persistent instance via deletePersistent(), state is " +
states[curr]);
}
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("StateTransitionsReturnedObjects: Unable to create persistent-nontransactional instance" +
" from a persistent-clean instance via makeNontransactional(), state is " +
states[curr]);
}
return null;
}
return obj;
}
/** */
public StateTransitionObj getPersistentNontransactionalDirtyInstance()
{
StateTransitionObj obj = getPersistentNontransactionalInstance();
if( obj == null ) return null;
obj.writeField(10000);
int curr = currentState(obj);
if( curr != PERSISTENT_NONTRANSACTIONAL_DIRTY ) {
if (debug) {
logger.debug("StateTransitionsReturnedObjects: Unable to create persistent-nontransactional-dirty instance" +
" from a persistent-clean instance via makeNontransactional()/JDOHelper.makeDirty," +
" state is " + states[curr]);
}
return null;
}
return obj;
}
/** */
public StateTransitionObj getDetachedCleanInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
obj = (StateTransitionObj) pm.detachCopy(obj);
int curr = currentState(obj);
if( curr != DETACHED_CLEAN ) {
if (debug) {
logger.debug("StateTransitionsReturnedObjects: Unable to create detached-clean instance" +
" from a persistent-clean instance via detachCopy," +
" state is " + states[curr]);
}
return null;
}
return obj;
}
/** */
public StateTransitionObj getDetachedDirtyInstance()
{
StateTransitionObj obj = getHollowInstance();
if( obj == null ) return null;
obj = (StateTransitionObj) pm.detachCopy(obj);
obj.writeField(1000);
int curr = currentState(obj);
if( curr != DETACHED_DIRTY ) {
if (debug) {
logger.debug("StateTransitionsReturnedObjects: Unable to create detached-dirty instance" +
" from a persistent-clean instance via detachCopy/persistent field modification," +
" state is " + states[curr]);
}
return null;
}
return obj;
}
}