ca.sqlpower.object.PersistedSPObjectTest.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.object.PersistedSPObjectTest.java

Source

/*
 * Copyright (c) 2009, SQL Power Group Inc.
 *
 * This file is part of SQL Power Library.
 *
 * SQL Power Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * SQL Power Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

package ca.sqlpower.object;

import java.awt.Image;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;

import ca.sqlpower.dao.PersistedSPOProperty;
import ca.sqlpower.dao.PersistedSPObject;
import ca.sqlpower.dao.PersisterUtils;
import ca.sqlpower.dao.SPPersister.DataType;
import ca.sqlpower.dao.SPPersisterListener;
import ca.sqlpower.dao.SPSessionPersister;
import ca.sqlpower.dao.session.SessionPersisterSuperConverter;
import ca.sqlpower.object.SPChildEvent.EventType;
import ca.sqlpower.object.annotation.Accessor;
import ca.sqlpower.object.annotation.Mutator;
import ca.sqlpower.object.annotation.NonProperty;
import ca.sqlpower.sql.DataSourceCollection;
import ca.sqlpower.sql.SPDataSource;
import ca.sqlpower.sqlobject.DatabaseConnectedTestCase;
import ca.sqlpower.sqlobject.SQLObjectRoot;
import ca.sqlpower.testutil.GenericNewValueMaker;
import ca.sqlpower.testutil.NewValueMaker;
import ca.sqlpower.testutil.SPObjectRoot;
import ca.sqlpower.testutil.TestUtils;
import ca.sqlpower.util.RunnableDispatcher;
import ca.sqlpower.util.StubWorkspaceContainer;
import ca.sqlpower.util.TransactionEvent;
import ca.sqlpower.util.WorkspaceContainer;

import com.google.common.collect.ImmutableList;

/**
 * Classes that implement SPObject and need to be persisted must implement
 * a test class that extends this test case.
 */
public abstract class PersistedSPObjectTest extends DatabaseConnectedTestCase {

    private static final Logger logger = Logger.getLogger(PersistedSPObjectTest.class);

    public class TestingSessionPersister extends SPSessionPersister {

        public TestingSessionPersister(String name, SPObject root, SessionPersisterSuperConverter converter) {
            super(name, root, converter);
        }

        @Override
        protected void refreshRootNode(PersistedSPObject pso) {
            //do nothing, this is not tested in a generic way.
        }

    }

    /**
     * Returns a class that is one of the child types of the object under test. An
     * object of this type must be able to be added as a child to the object without
     * error. If the object under test does not allow children or all of the children
     * of the object are final so none can be added, null will be returned.
     */
    protected abstract Class<? extends SPObject> getChildClassType();

    /**
     * This workspace contains the root SPObject made in setup. This is only needed
     * for connecting the root to a session in setup. If a formal root object
     * for a session gets created in the library it can replace this stub version. 
     */
    public static class StubWorkspace extends AbstractSPObject {

        public static final List<Class<? extends SPObject>> allowedChildTypes = new ImmutableList.Builder<Class<? extends SPObject>>()
                .add(SPObject.class).build();

        private final WorkspaceContainer workspaceContainer;
        private final RunnableDispatcher dispatcher;

        private final SPObjectRoot root;

        public StubWorkspace(WorkspaceContainer workspaceContainer, RunnableDispatcher dispatcher,
                SPObjectRoot root) {
            this.workspaceContainer = workspaceContainer;
            this.dispatcher = dispatcher;
            this.root = root;
        }

        @Override
        protected boolean removeChildImpl(SPObject child) {
            return false;
        }

        public List<Class<? extends SPObject>> getAllowedChildTypes() {
            return allowedChildTypes;
        }

        public List<? extends SPObject> getChildren() {
            return Collections.singletonList(root);
        }

        public List<? extends SPObject> getDependencies() {
            return Collections.emptyList();
        }

        public void removeDependency(SPObject dependency) {
            //do nothing
        }

        @Override
        public WorkspaceContainer getWorkspaceContainer() {
            return workspaceContainer;
        }

        @Override
        public RunnableDispatcher getRunnableDispatcher() {
            return dispatcher;
        }
    }

    /**
     * Used in roll back tests. If an exception is thrown due to failing a test
     * the rollback will catch the exception and try to roll back the object.
     * However, we want to know if the persist failed before rolling back. This
     * object will store the failed reason during persist.
     */
    private Throwable failureReason;

    private SPObjectRoot root;

    /**
     * This is a generic converter that works off the root object and pl.ini in
     * this test. This will need to be set to a different converter in tests
     * that are outside of the library.
     */
    private SessionPersisterSuperConverter converter;

    public PersistedSPObjectTest(String name) {
        super(name);
    }

    public PersistedSPObjectTest(String name, boolean setupDB) {
        super(name, setupDB);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        root = new SPObjectRoot();
        StubWorkspaceContainer stub = new StubWorkspaceContainer() {
            private final SPObject workspace = new StubWorkspace(this, this, root);

            @Override
            public SPObject getWorkspace() {
                return workspace;
            }
        };
        root.setParent(stub.getWorkspace());
        SQLObjectRoot sqlRoot = new SQLObjectRoot();
        root.addChild(sqlRoot, 0);
        if (setupDB) {
            sqlRoot.addDatabase(db, 0);
        }

        converter = new SessionPersisterSuperConverter(getPLIni(), root);
    }

    /**
     * Returns an object of the type being tested. Will be used in reflective
     * tests being done for persisting objects. This must be a descendant of the
     * root object returned from {@link #getRootObject()}.
     */
    public abstract SPObject getSPObjectUnderTest();

    /**
     * Returns the converter to be used in persister tests. This can be
     * overridden by other test classes to specify a converter that is different
     * from the basic one in the library.
     */
    public SessionPersisterSuperConverter getConverter() {
        return converter;
    }

    /**
     * Returns a new new-value-maker that will attach {@link SPObject}s to the given root.
     * Classes extending this test may want to override this method to create a different
     * type of new-value-maker that creates more types of new values.
     */
    public NewValueMaker createNewValueMaker(SPObject root, DataSourceCollection<SPDataSource> dsCollection) {
        return new GenericNewValueMaker(root, dsCollection);
    }

    public SPObject getRootObject() {
        return root;
    }

    /**
     * This function is here to be overidden in special cases. eg = MungeProcess.
     * Since there is a final child of ResultStep and the child type, you need to
     * offset the index you add the child add by 1.
     */
    public int getIndexToInsertChildAt() {
        return 0;
    }

    /**
     * All persistable {@link SPObject} implementations must define a static
     * final field which is a list defining the absolute ordering of that
     * class's child type classes. This method ensures that list is retrievable
     * by reflection from the object, that the field is public, static, and
     * final, and that it is nonempty for classes that allow children and empty
     * for classes that do not allow children.
     */
    @SuppressWarnings("unchecked")
    public void testAllowedChildTypesField() throws Exception {
        Class<? extends SPObject> classUnderTest = getSPObjectUnderTest().getClass();
        Field childOrderField;
        try {
            childOrderField = classUnderTest.getDeclaredField("allowedChildTypes");
        } catch (NoSuchFieldException ex) {
            fail("Persistent " + classUnderTest + " must have a static final field called allowedChildTypes");
            throw new AssertionError(); // NOTREACHED
        }

        assertEquals("The allowedChildTypes field must be final", true,
                Modifier.isFinal(childOrderField.getModifiers()));

        assertEquals("The allowedChildTypes field must be static", true,
                Modifier.isStatic(childOrderField.getModifiers()));

        // Note: in the future, we will change this to require that the field is private
        assertEquals("The allowedChildTypes field must be public", true,
                Modifier.isPublic(childOrderField.getModifiers()));

        List<Class<? extends SPObject>> allowedChildTypes = (List<Class<? extends SPObject>>) childOrderField
                .get(null);
        if (getSPObjectUnderTest().allowsChildren()) {
            assertFalse(allowedChildTypes.isEmpty());
        } else {
            assertTrue(allowedChildTypes.isEmpty());
        }
    }

    /**
     * Tests the SPPersisterListener will persist a property change to its
     * target persister.
     */
    public void testSPListenerPersistsProperty() throws Exception {
        CountingSPPersister countingPersister = new CountingSPPersister();
        SPPersisterListener listener = new SPPersisterListener(countingPersister, getConverter());
        NewValueMaker valueMaker = createNewValueMaker(root, getPLIni());

        SPObject wo = getSPObjectUnderTest();
        wo.addSPListener(listener);

        List<PropertyDescriptor> settableProperties;
        settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass()));

        Set<String> propertiesToPersist = findPersistableBeanProperties(false, false);

        for (PropertyDescriptor property : settableProperties) {
            Object oldVal;

            if (!propertiesToPersist.contains(property.getName()))
                continue;

            countingPersister.clearAllPropertyChanges();
            try {
                oldVal = PropertyUtils.getSimpleProperty(wo, property.getName());

                // check for a setter
                if (property.getWriteMethod() == null)
                    continue;

            } catch (NoSuchMethodException e) {
                logger.debug(
                        "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName());
                continue;
            }

            Object newVal = valueMaker.makeNewValue(property.getPropertyType(), oldVal, property.getName());
            int oldChangeCount = countingPersister.getPersistPropertyCount();

            try {
                //The first property change at current is always the property change we are
                //looking for, this may need to be changed in the future to find the correct
                //property.
                PersistedSPOProperty propertyChange = null;

                try {
                    logger.debug("Setting property '" + property.getName() + "' to '" + newVal + "' ("
                            + newVal.getClass().getName() + ")");
                    wo.setMagicEnabled(false);
                    BeanUtils.copyProperty(wo, property.getName(), newVal);

                    assertTrue("Did not persist property " + property.getName(),
                            oldChangeCount < countingPersister.getPersistPropertyCount());

                    for (PersistedSPOProperty nextPropertyChange : countingPersister.getPersistPropertyList()) {
                        if (nextPropertyChange.getPropertyName().equals(property.getName())) {
                            propertyChange = nextPropertyChange;
                            break;
                        }
                    }
                    assertNotNull("A property change event cannot be found for the property " + property.getName(),
                            propertyChange);

                    assertEquals(wo.getUUID(), propertyChange.getUUID());
                    assertEquals(property.getName(), propertyChange.getPropertyName());

                    assertEquals(
                            "Old value of property " + property.getName() + " was wrong, value expected was  "
                                    + oldVal + " but is " + countingPersister.getLastOldValue(),
                            getConverter().convertToBasicType(oldVal), propertyChange.getOldValue());

                } finally {
                    wo.setMagicEnabled(true);
                }

                //Input streams from images are being compared by hash code not values
                if (Image.class.isAssignableFrom(property.getPropertyType())) {
                    logger.debug(propertyChange.getNewValue().getClass());
                    assertTrue(Arrays.equals(PersisterUtils.convertImageToStreamAsPNG((Image) newVal).toByteArray(),
                            PersisterUtils
                                    .convertImageToStreamAsPNG((Image) getConverter()
                                            .convertToComplexType(propertyChange.getNewValue(), Image.class))
                                    .toByteArray()));
                } else {
                    assertEquals(getConverter().convertToBasicType(newVal), propertyChange.getNewValue());
                }
                Class<? extends Object> classType;
                if (oldVal != null) {
                    classType = oldVal.getClass();
                } else {
                    classType = newVal.getClass();
                }
                assertEquals(PersisterUtils.getDataType(classType), propertyChange.getDataType());
            } catch (InvocationTargetException e) {
                logger.debug("(non-fatal) Failed to write property '" + property.getName() + " to type "
                        + wo.getClass().getName());
            }
        }
    }

    /**
     * Tests the {@link SPSessionPersister} can update every settable property
     * on an object based on a persist call.
     */
    public void testSPPersisterPersistsProperties() throws Exception {
        SPSessionPersister persister = new TestingSessionPersister("Testing Persister", root, getConverter());
        persister.setWorkspaceContainer(root.getWorkspaceContainer());
        NewValueMaker valueMaker = createNewValueMaker(root, getPLIni());

        SPObject objectUnderTest = getSPObjectUnderTest();

        List<PropertyDescriptor> settableProperties = Arrays
                .asList(PropertyUtils.getPropertyDescriptors(objectUnderTest.getClass()));

        Set<String> propertiesToPersist = findPersistableBeanProperties(false, false);

        for (PropertyDescriptor property : settableProperties) {
            Object oldVal;

            //Changing the UUID of the object makes it referenced as a different object
            //and would make the check later in this test fail.
            if (property.getName().equals("UUID"))
                continue;

            if (!propertiesToPersist.contains(property.getName()))
                continue;

            try {
                oldVal = PropertyUtils.getSimpleProperty(objectUnderTest, property.getName());

                // check for a setter
                if (property.getWriteMethod() == null)
                    continue;

            } catch (NoSuchMethodException e) {
                logger.debug("Skipping non-settable property " + property.getName() + " on "
                        + objectUnderTest.getClass().getName());
                continue;
            }

            //special case for parent types. If a specific wabit object has a tighter parent then
            //WabitObject the getParentClass should return the parent type.
            Class<?> propertyType = property.getPropertyType();
            if (property.getName().equals("parent")) {
                propertyType = getSPObjectUnderTest().getClass().getMethod("getParent").getReturnType();
                logger.debug("Persisting parent, type is " + propertyType);
            }
            Object newVal = valueMaker.makeNewValue(propertyType, oldVal, property.getName());

            System.out.println("Persisting property \"" + property.getName() + "\" from oldVal \"" + oldVal
                    + "\" to newVal \"" + newVal + "\"");

            DataType type = PersisterUtils.getDataType(property.getPropertyType());
            Object basicNewValue = getConverter().convertToBasicType(newVal);
            persister.begin();
            persister.persistProperty(objectUnderTest.getUUID(), property.getName(), type,
                    getConverter().convertToBasicType(oldVal), basicNewValue);
            persister.commit();

            Object newValAfterSet = PropertyUtils.getSimpleProperty(objectUnderTest, property.getName());
            Object basicExpectedValue = getConverter().convertToBasicType(newValAfterSet);

            assertPersistedValuesAreEqual(newVal, newValAfterSet, basicNewValue, basicExpectedValue,
                    property.getPropertyType());
        }
    }

    /**
     * This test will be run for each object that extends SPObject and confirms
     * the SPSessionPersister can create new objects 
     * @throws Exception
     */
    public void testPersisterCreatesNewObjects() throws Exception {
        SPObjectRoot newRoot = new SPObjectRoot();
        WorkspaceContainer stub = new StubWorkspaceContainer() {
            private final SPObject workspace = new StubWorkspace(this, this, root);

            @Override
            public SPObject getWorkspace() {
                return workspace;
            }
        };
        newRoot.setParent(stub.getWorkspace());
        NewValueMaker valueMaker = createNewValueMaker(root, getPLIni());

        NewValueMaker newValueMaker = createNewValueMaker(newRoot, getPLIni());

        SessionPersisterSuperConverter newConverter = new SessionPersisterSuperConverter(getPLIni(), newRoot);

        SPSessionPersister persister = new TestingSessionPersister("Test persister", newRoot, newConverter);
        persister.setWorkspaceContainer(stub);

        for (SPObject child : root.getChildren()) {
            copyToRoot(child, newValueMaker);
        }

        SPObject objectUnderTest = getSPObjectUnderTest();

        Set<String> propertiesToPersist = findPersistableBeanProperties(false, false);

        List<PropertyDescriptor> settableProperties = Arrays
                .asList(PropertyUtils.getPropertyDescriptors(objectUnderTest.getClass()));

        //set all properties of the object
        for (PropertyDescriptor property : settableProperties) {
            Object oldVal;
            if (!propertiesToPersist.contains(property.getName()))
                continue;
            if (property.getName().equals("parent"))
                continue; //Changing the parent causes headaches.

            try {
                oldVal = PropertyUtils.getSimpleProperty(objectUnderTest, property.getName());

                // check for a setter
                if (property.getWriteMethod() == null)
                    continue;

            } catch (NoSuchMethodException e) {
                logger.debug("Skipping non-settable property " + property.getName() + " on "
                        + objectUnderTest.getClass().getName());
                continue;
            }

            Object newVal = valueMaker.makeNewValue(property.getPropertyType(), oldVal, property.getName());
            Object newValInNewRoot = newValueMaker.makeNewValue(property.getPropertyType(), oldVal,
                    property.getName());
            if (newValInNewRoot instanceof SPObject) {
                ((SPObject) newValInNewRoot).setUUID(((SPObject) newVal).getUUID());
            }

            try {
                logger.debug("Setting property '" + property.getName() + "' to '" + newVal + "' ("
                        + newVal.getClass().getName() + ")");
                BeanUtils.copyProperty(objectUnderTest, property.getName(), newVal);

            } catch (InvocationTargetException e) {
                logger.debug("(non-fatal) Failed to write property '" + property.getName() + " to type "
                        + objectUnderTest.getClass().getName());
            }
        }

        //create a new root and parent for the object
        SPObject newParent;
        if (objectUnderTest.getParent() instanceof SPObjectRoot) {
            newParent = newRoot;
        } else {
            newParent = (SPObject) newValueMaker.makeNewValue(objectUnderTest.getParent().getClass(), null, "");
        }
        newParent.setUUID(objectUnderTest.getParent().getUUID());

        int childCount = newParent.getChildren().size();

        //persist the object to the new target root
        Class<? extends SPObject> classChildType = PersisterUtils.getParentAllowedChildType(
                objectUnderTest.getClass().getName(), objectUnderTest.getParent().getClass().getName());
        new SPPersisterListener(persister, getConverter()).persistObject(objectUnderTest,
                objectUnderTest.getParent().getChildren(classChildType).indexOf(objectUnderTest));

        //check object exists
        assertEquals(childCount + 1, newParent.getChildren().size());
        SPObject newChild = null;
        for (SPObject child : newParent.getChildren()) {
            if (child.getUUID().equals(objectUnderTest.getUUID())) {
                newChild = child;
                break;
            }
        }
        if (newChild == null)
            fail("The child was not correctly persisted.");

        //check all interesting properties
        for (PropertyDescriptor property : settableProperties) {
            if (!propertiesToPersist.contains(property.getName()))
                continue;
            if (property.getName().equals("parent"))
                continue; //Changing the parent causes headaches.

            Method readMethod = property.getReadMethod();

            Object valueBeforePersist = readMethod.invoke(objectUnderTest);
            Object valueAfterPersist = readMethod.invoke(newChild);
            Object basicValueBeforePersist = getConverter().convertToBasicType(valueBeforePersist);
            Object basicValueAfterPersist = newConverter.convertToBasicType(valueAfterPersist);

            assertPersistedValuesAreEqual(valueBeforePersist, valueAfterPersist, basicValueBeforePersist,
                    basicValueAfterPersist, readMethod.getReturnType());
        }
    }

    /**
     * Helper method for making one object tree contain the same values of the
     * other tree. The objects created in the new root are not guaranteed to
     * have the same hierarchy as the original parent-child ordering but is fine
     * for current testing.
     * 
     * @param child
     *            The child object that a new object of the same type will be
     *            created and added to the new root. All of its descendants will
     *            be added to the new root as well.
     * @param newValueMaker
     *            A {@link NewValueMaker} containing the root of the new object
     *            tree that can have new children added to it.
     */
    private void copyToRoot(SPObject child, NewValueMaker newValueMaker) {
        if (child != getSPObjectUnderTest()) {
            if (getSPObjectUnderTest().getParent() != null && child == getSPObjectUnderTest().getParent())
                return;
            SPObject newValue = (SPObject) newValueMaker.makeNewValue(child.getClass(), child, "Duplicated child");
            newValue.setUUID(child.getUUID());
            for (SPObject descendant : child.getChildren()) {
                copyToRoot(descendant, newValueMaker);
            }
        }
    }

    /**
     * Tests passing an object to an {@link SPPersisterListener} will persist
     * the object and all of the properties that have setters.
     */
    public void testSPListenerPersistsNewObjects() throws Exception {
        CountingSPPersister persister = new CountingSPPersister();
        NewValueMaker valueMaker = createNewValueMaker(root, getPLIni());

        SPObject objectUnderTest = getSPObjectUnderTest();

        Set<String> propertiesToPersist = findPersistableBeanProperties(false, false);

        List<PropertyDescriptor> settableProperties = Arrays
                .asList(PropertyUtils.getPropertyDescriptors(objectUnderTest.getClass()));

        //set all properties of the object
        for (PropertyDescriptor property : settableProperties) {
            Object oldVal;
            if (!propertiesToPersist.contains(property.getName()))
                continue;
            if (property.getName().equals("parent"))
                continue; //Changing the parent causes headaches.

            try {
                oldVal = PropertyUtils.getSimpleProperty(objectUnderTest, property.getName());

                // check for a setter
                if (property.getWriteMethod() == null)
                    continue;

            } catch (NoSuchMethodException e) {
                logger.debug("Skipping non-settable property " + property.getName() + " on "
                        + objectUnderTest.getClass().getName());
                continue;
            }

            Object newVal = valueMaker.makeNewValue(property.getPropertyType(), oldVal, property.getName());

            try {
                logger.debug("Setting property '" + property.getName() + "' to '" + newVal + "' ("
                        + newVal.getClass().getName() + ")");
                BeanUtils.copyProperty(objectUnderTest, property.getName(), newVal);

            } catch (InvocationTargetException e) {
                logger.debug("(non-fatal) Failed to write property '" + property.getName() + " to type "
                        + objectUnderTest.getClass().getName());
            }
        }

        //persist the object to the new target root
        new SPPersisterListener(persister, getConverter()).persistObject(objectUnderTest,
                objectUnderTest.getParent().getChildren(objectUnderTest.getClass()).indexOf(objectUnderTest));

        assertTrue(persister.getPersistPropertyCount() > 0);

        assertEquals(getSPObjectUnderTest().getUUID(), persister.getPersistObjectList().get(0).getUUID());

        //set all properties of the object
        for (PropertyDescriptor property : settableProperties) {
            Object oldVal;
            if (!propertiesToPersist.contains(property.getName()))
                continue;
            if (property.getName().equals("parent"))
                continue; //Changing the parent causes headaches.

            try {
                oldVal = PropertyUtils.getSimpleProperty(objectUnderTest, property.getName());

                // check for a setter
                if (property.getWriteMethod() == null)
                    continue;

            } catch (NoSuchMethodException e) {
                logger.debug("Skipping non-settable property " + property.getName() + " on "
                        + objectUnderTest.getClass().getName());
                continue;
            }

            Object newValue = null;

            boolean found = false;
            for (PersistedSPOProperty persistedSPO : persister.getPersistPropertyList()) {
                if (persistedSPO.getPropertyName().equals(property.getName())
                        && persistedSPO.getUUID().equals(getSPObjectUnderTest().getUUID())) {
                    newValue = persistedSPO.getNewValue();
                    found = true;
                    break;
                }
            }

            assertTrue("Could not find the persist call for property " + property.getName(), found);

            if (oldVal == null) {
                assertNull(newValue);
            } else {
                assertPersistedValuesAreEqual(oldVal,
                        getConverter().convertToComplexType(newValue, oldVal.getClass()),
                        getConverter().convertToBasicType(oldVal), newValue, property.getPropertyType());
            }
        }
    }

    /**
     * This method can be overridden by extending classes to specify properties
     * that should not be written to by the roll back test. The properties put
     * in this list should have a good reason for not being used in the test as
     * skipping a property defeats the purpose of the test.
     */
    public Set<String> getRollbackTestIgnorePropertySet() {
        return new HashSet<String>();
    }

    /**
     * This test will make changes to the {@link SPObject} under test and then
     * cause an exception forcing the persister to roll back the changes in the
     * object.
     * <p>
     * Both the changes have to come through the persister initially before the
     * exception and they have to be reset after the exception.
     */
    public void testSessionPersisterRollsBackProperties() throws Exception {
        SPObject objectUnderTest = getSPObjectUnderTest();
        final Map<PropertyDescriptor, Object> initialProperties = new HashMap<PropertyDescriptor, Object>();
        final Map<PropertyDescriptor, Object> newProperties = new HashMap<PropertyDescriptor, Object>();

        List<PropertyDescriptor> settableProperties = Arrays
                .asList(PropertyUtils.getPropertyDescriptors(objectUnderTest.getClass()));

        Set<String> propertiesToPersist = findPersistableBeanProperties(false, false);

        Set<String> ignorePropertySet = getRollbackTestIgnorePropertySet();

        NewValueMaker valueMaker = createNewValueMaker(getRootObject(), getPLIni());

        SPSessionPersister persister = new TestingSessionPersister("tester", getRootObject(), getConverter());
        persister.setWorkspaceContainer(getRootObject().getWorkspaceContainer());

        failureReason = null;

        SPPersisterListener listener = new SPPersisterListener(new CountingSPPersister(), converter) {

            private boolean transactionAlreadyFinished = false;

            @Override
            public void transactionEnded(TransactionEvent e) {
                if (transactionAlreadyFinished)
                    return;
                transactionAlreadyFinished = true;
                try {
                    for (Map.Entry<PropertyDescriptor, Object> newProperty : newProperties.entrySet()) {
                        Object objectUnderTest = getSPObjectUnderTest();
                        Object newVal = newProperty.getValue();
                        Object basicNewValue = converter.convertToBasicType(newVal);

                        Object newValAfterSet = PropertyUtils.getSimpleProperty(objectUnderTest,
                                newProperty.getKey().getName());
                        Object basicExpectedValue = converter.convertToBasicType(newValAfterSet);

                        logger.debug("Testing property " + newProperty.getKey().getName());
                        assertPersistedValuesAreEqual(newVal, newValAfterSet, basicNewValue, basicExpectedValue,
                                newProperty.getKey().getPropertyType());
                    }
                } catch (Throwable ex) {
                    failureReason = ex;
                    throw new RuntimeException(ex);
                }
                throw new RuntimeException("Forcing rollback.");
            }
        };
        //Transactions begin and commits are currently sent on the workspace.
        getRootObject().getParent().addSPListener(listener);

        persister.begin();
        for (PropertyDescriptor property : settableProperties) {
            Object oldVal;

            //Changing the UUID of the object makes it referenced as a different object
            //and would make the check later in this test fail.
            if (property.getName().equals("UUID"))
                continue;

            if (!propertiesToPersist.contains(property.getName()))
                continue;

            if (ignorePropertySet.contains(property.getName()))
                continue;

            try {
                oldVal = PropertyUtils.getSimpleProperty(objectUnderTest, property.getName());

                // check for a setter
                if (property.getWriteMethod() == null)
                    continue;

            } catch (NoSuchMethodException e) {
                logger.debug("Skipping non-settable property " + property.getName() + " on "
                        + objectUnderTest.getClass().getName());
                continue;
            }

            initialProperties.put(property, oldVal);

            //special case for parent types. If a specific wabit object has a tighter parent then
            //WabitObject the getParentClass should return the parent type.
            Class<?> propertyType = property.getPropertyType();
            if (property.getName().equals("parent")) {
                propertyType = getSPObjectUnderTest().getClass().getMethod("getParent").getReturnType();
                logger.debug("Persisting parent, type is " + propertyType);
            }
            Object newVal = valueMaker.makeNewValue(propertyType, oldVal, property.getName());

            DataType type = PersisterUtils.getDataType(property.getPropertyType());
            Object basicNewValue = converter.convertToBasicType(newVal);
            persister.begin();
            persister.persistProperty(objectUnderTest.getUUID(), property.getName(), type,
                    converter.convertToBasicType(oldVal), basicNewValue);
            persister.commit();

            newProperties.put(property, newVal);
        }

        try {
            persister.commit();
            fail("An exception should make the persister hit the exception block.");
        } catch (Exception e) {
            //continue, exception expected.
        }

        if (failureReason != null) {
            throw new RuntimeException("Failed when asserting properties were " + "fully persisted.",
                    failureReason);
        }

        for (Map.Entry<PropertyDescriptor, Object> entry : initialProperties.entrySet()) {
            assertEquals("Property " + entry.getKey().getName() + " did not match after rollback.",
                    entry.getValue(), PropertyUtils.getSimpleProperty(objectUnderTest, entry.getKey().getName()));
        }
    }

    /**
     * Taken from AbstractWabitObjectTest. When Wabit is using annotations
     * remove this method from that class as this class will be doing the reflective
     * tests.
     * <p>
     * Tests that the new value that was persisted is the same as an old value
     * that was to be persisted. This helper method for the persister tests will
     * compare the values by their converted type or some other means as not all
     * values that are persisted have implemented their equals method.
     * <p>
     * This will do the asserts to compare if the objects are equal.
     * 
     * @param valueBeforePersist
     *            the value that we are expecting the persisted value to contain
     * @param valueAfterPersist
     *            the value that was persisted to the object. This will be
     *            tested against the valueBeforePersist to ensure that they are
     *            the same.
     * @param basicValueBeforePersist
     *            The valueBeforePersist converted to a basic type by a
     *            converter.
     * @param basicValueAfterPersist
     *            The valueAfterPersist converted to a basic type by a
     *            converter.
     * @param valueType
     *            The type of object the before and after values should contain.
     */
    private void assertPersistedValuesAreEqual(Object valueBeforePersist, Object valueAfterPersist,
            Object basicValueBeforePersist, Object basicValueAfterPersist, Class<? extends Object> valueType) {

        //Input streams from images are being compared by hash code not values
        if (Image.class.isAssignableFrom(valueType)) {
            assertTrue(Arrays.equals(
                    PersisterUtils.convertImageToStreamAsPNG((Image) valueBeforePersist).toByteArray(),
                    PersisterUtils.convertImageToStreamAsPNG((Image) valueAfterPersist).toByteArray()));
        } else if (Exception.class.isAssignableFrom(valueType)) {
            //Comparing only the first part of the exception strings as the new exception created by the persistence class
            //will have the converter after the original stack trace since that is where the exception was made.
            assertTrue("Persist failed for type " + valueType,
                    ((String) basicValueAfterPersist).startsWith((String) basicValueBeforePersist));
        } else {

            //Not all new values are equivalent to their old values so we are
            //comparing them by their basic type as that is at least comparable, in most cases, i hope.
            assertEquals("Persist failed for type " + valueType, basicValueBeforePersist, basicValueAfterPersist);
        }
    }

    /**
     * Ensures that each getter and setter in the object under test is annotated
     * in some way. This way methods that need to be annotated to be persisted
     * will not be missed or will be defined to be skipped. The annotations are either
     * {@link Accessor} for getters, {@link Mutator} for setters, and {@link NonProperty}
     * that is neither an accessor or mutator but looks like one.
     */
    public void testGettersAndSettersPersistedAnnotated() throws Exception {
        findPersistableBeanProperties(false, false);
    }

    protected Set<String> findPersistableBeanProperties(boolean includeTransient,
            boolean includeConstructorMutators) throws Exception {
        return TestUtils.findPersistableBeanProperties(getSPObjectUnderTest(), includeTransient,
                includeConstructorMutators);
    }

    /**
     * Tests a child can be added to the {@link SPObject} under test. If the
     * object does not allow children then this test will return early. This
     * test is used as a start to the remove child test.
     * 
     * @return The child that was added or null if no child was added.
     * @throws Exception
     */
    public SPObject testSPPersisterAddsChild() throws Exception {
        NewValueMaker valueMaker = createNewValueMaker(root, getPLIni());

        SPObject spObject = getSPObjectUnderTest();
        int oldChildCount = spObject.getChildren().size();
        if (!spObject.allowsChildren())
            return null;

        Class<? extends SPObject> childClassType = getChildClassType();
        if (childClassType == null)
            return null;

        SPSessionPersister persister = new TestingSessionPersister("test", getSPObjectUnderTest(), getConverter());
        persister.setWorkspaceContainer(getSPObjectUnderTest().getWorkspaceContainer());
        SPPersisterListener listener = new SPPersisterListener(persister, getConverter());

        SPObject newChild = (SPObject) valueMaker.makeNewValue(childClassType, null, "child");
        newChild.setParent(spObject);

        listener.childAdded(
                new SPChildEvent(spObject, childClassType, newChild, getIndexToInsertChildAt(), EventType.ADDED));

        assertEquals(oldChildCount + 1, spObject.getChildren().size());
        assertEquals(newChild, spObject.getChildren(childClassType).get(getIndexToInsertChildAt()));

        newChild.removeSPListener(listener);

        //Find the actual child under the object under test as the persister will make a new,
        //different object to add not the newChild object. This lets the objects compare
        //equal by reference.
        for (SPObject existingChild : spObject.getChildren(childClassType)) {
            if (existingChild.getUUID().equals(newChild.getUUID())) {
                return existingChild;
            }
        }
        return null;
    }

    /**
     * Confirms a child can be removed from an object it was previously added to.
     * This uses {@link #testSPPersisterAddsChild()} as a starting point.
     */
    public void testSPPersisterRemovesChild() throws Exception {
        if (!getSPObjectUnderTest().allowsChildren())
            return;
        SPObject child = testSPPersisterAddsChild();
        if (child == null)
            return;

        SPSessionPersister persister = new TestingSessionPersister("test", getSPObjectUnderTest(), getConverter());
        persister.setWorkspaceContainer(getSPObjectUnderTest().getWorkspaceContainer());
        SPPersisterListener listener = new SPPersisterListener(persister, getConverter());

        int childCount = getSPObjectUnderTest().getChildren().size();

        listener.childRemoved(new SPChildEvent(getSPObjectUnderTest(), child.getClass(), child,
                getIndexToInsertChildAt(), EventType.REMOVED));

        assertEquals(childCount - 1, getSPObjectUnderTest().getChildren().size());
        assertFalse(getSPObjectUnderTest().getChildren().contains(child));
    }

    /**
     * Tests that the parent property of a child is only and must set to null
     * after firing a child removed event. If the parent is set to null before
     * firing the child removed event,
     * {@link SPPersisterListener#propertyChanged(java.beans.PropertyChangeEvent)}
     * will throw an exception because its root object does not have a runnable
     * dispatcher reference. If the parent is not set to null after firing the
     * child removed event, anything that still has a reference to the child
     * could call getParent() and be returned a non-null value which is
     * incorrect and misleading.
     */
    public void testNullParentSetAfterChildRemovedEvent() throws Exception {
        if (!getSPObjectUnderTest().allowsChildren())
            return;
        SPObject child = testSPPersisterAddsChild();
        if (child == null)
            return;

        SPListener listener = new AbstractSPListener() {
            @Override
            public void childRemoved(SPChildEvent e) {
                assertNotNull(
                        "Parent of " + e.getChildType() + " must not be set to null before calling removeChild.",
                        e.getChild().getParent());
            }
        };

        getSPObjectUnderTest().addSPListener(listener);

        getSPObjectUnderTest().setMagicEnabled(false);
        getSPObjectUnderTest().removeChild(child);
        getSPObjectUnderTest().setMagicEnabled(true);

        assertNull("Parent of " + child.getClass() + " must be set to null after calling removeChild.",
                child.getParent());
    }

    public void testRemoveChildFiresEvent() throws Exception {
        if (!getSPObjectUnderTest().allowsChildren())
            return;
        SPObject child = testSPPersisterAddsChild();
        if (child == null)
            return;

        CountingSPListener listener = new CountingSPListener();

        getSPObjectUnderTest().addSPListener(listener);

        getSPObjectUnderTest().setMagicEnabled(false);
        getSPObjectUnderTest().removeChild(child);
        getSPObjectUnderTest().setMagicEnabled(true);

        assertEquals(1, listener.getChildRemovedCount());
    }

    public void testAddChildFiresEvents() throws Exception {
        SPObject o = getSPObjectUnderTest();

        if (!o.allowsChildren())
            return;

        Class<?> childClassType = getChildClassType();
        if (childClassType == null)
            return;

        CountingSPListener listener = new CountingSPListener();

        o.addSPListener(listener);

        NewValueMaker valueMaker = createNewValueMaker(root, getPLIni());
        SPObject newChild = (SPObject) valueMaker.makeNewValue(childClassType, null, "child");

        o.addChild(newChild, getIndexToInsertChildAt());

        assertEquals(1, listener.getChildAddedCount());
    }

}