org.openvpms.component.business.service.archetype.ArchetypeServiceActTestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.openvpms.component.business.service.archetype.ArchetypeServiceActTestCase.java

Source

/*
 *  Version: 1.0
 *
 *  The contents of this file are subject to the OpenVPMS License Version
 *  1.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.openvpms.org/license/
 *
 *  Software distributed under the License is distributed on an 'AS IS' basis,
 *  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 *  for the specific language governing rights and limitations under the
 *  License.
 *
 *  Copyright 2005 (C) OpenVPMS Ltd. All Rights Reserved.
 *
 *  $Id$
 */

package org.openvpms.component.business.service.archetype;

import org.apache.commons.lang.StringUtils;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.openvpms.component.business.dao.hibernate.im.IMObjectDAOHibernate;
import org.openvpms.component.business.dao.im.common.IMObjectDAOException;
import org.openvpms.component.business.domain.im.act.Act;
import org.openvpms.component.business.domain.im.act.ActRelationship;
import org.openvpms.component.business.domain.im.archetype.descriptor.ArchetypeDescriptor;
import org.openvpms.component.business.domain.im.archetype.descriptor.NodeDescriptor;
import org.openvpms.component.business.domain.im.common.Entity;
import org.openvpms.component.business.domain.im.common.IMObject;
import org.openvpms.component.business.domain.im.common.IMObjectReference;
import org.openvpms.component.business.domain.im.common.Participation;
import org.openvpms.component.business.domain.im.party.Party;
import org.openvpms.component.business.service.AbstractArchetypeServiceTest;
import static org.openvpms.component.business.service.archetype.ArchetypeServiceException.ErrorCode.FailedToDeleteObject;
import static org.openvpms.component.business.service.archetype.ArchetypeServiceException.ErrorCode.FailedToSaveCollectionOfObjects;
import org.openvpms.component.business.service.archetype.helper.ActBean;
import org.openvpms.component.business.service.archetype.helper.ArchetypeQueryHelper;
import org.openvpms.component.system.common.query.ArchetypeQuery;
import org.openvpms.component.system.common.query.IPage;
import org.openvpms.component.system.common.query.NodeConstraint;
import org.openvpms.component.system.common.query.RelationalOp;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * Test that ability to create and query on acts.
 *
 * @author <a href="mailto:support@openvpms.org">OpenVPMS Team</a>
 * @version $LastChangedDate$
 */
@ContextConfiguration("archetype-service-appcontext.xml")
public class ArchetypeServiceActTestCase extends AbstractArchetypeServiceTest {

    /**
     * The transaction template.
     */
    private TransactionTemplate template;

    /**
     * Test the creation of a simple act.
     */
    @Test
    public void testSimpleActCreation() {
        Party person = createPerson("MR", "Jim", "Alateras");
        save(person);
        Act act = createSimpleAct("study", "inprogress");
        Participation participation = createSimpleParticipation("studyParticipation", person, act);
        act.addParticipation(participation);
        save(act);

        Act act1 = (Act) get(act.getObjectReference());
        assertEquals(act1, act);
    }

    /**
     * Test the search by acts function.
     */
    @Test
    public void testGetActs() {
        // create an act which participates in 5 acts
        Party person = createPerson("MR", "Jim", "Alateras");
        save(person);
        for (int index = 0; index < 5; index++) {
            Act act = createSimpleAct("study" + index, "inprogress");
            Participation participation = createSimpleParticipation("studyParticipation", person, act);
            act.addParticipation(participation);
            save(act);
        }

        // now use the getActs request
        IPage acts = ArchetypeQueryHelper.getActs(getArchetypeService(), person.getObjectReference(),
                "participation.simple", "act", "simple", null, null, null, null, null, false, 0,
                ArchetypeQuery.ALL_RESULTS);
        assertEquals(5, acts.getTotalResults());

        // now look at the paging aspects
        acts = ArchetypeQueryHelper.getActs(getArchetypeService(), person.getObjectReference(),
                "participation.simple", "act", "simple", null, null, null, null, null, false, 0, 1);
        assertEquals(5, acts.getTotalResults());
        assertEquals(1, acts.getResults().size());
        assertFalse(StringUtils.isEmpty(((Act) acts.getResults().get(0)).getName()));
    }

    /**
     * Retrieve acts using a start and end date.
     */
    @Test
    public void testGetActsBetweenTimes() {
        Date startTime = new Date();
        Date endTime = new Date(startTime.getTime() + 2 * 60 * 60 * 1000);
        ArchetypeQuery query = new ArchetypeQuery("act.simple", false, true)
                .add(new NodeConstraint("startTime", RelationalOp.BTW, startTime, endTime))
                .add(new NodeConstraint("name", "between"));
        int acount = get(query).size();
        save(createSimpleAct("between", "start"));
        int acount1 = get(query).size();
        assertEquals(acount + 1, acount1);

        for (int index = 0; index < 5; index++) {
            save(createSimpleAct("between", "start"));
        }
        acount1 = get(query).size();
        assertEquals(acount + 6, acount1);
    }

    /**
     * Tests OVPMS-211.
     */
    @Test
    public void testOVPMS211() {
        Act estimationItem1 = (Act) create("act.customerEstimationItem");
        ActBean estimationItem1Bean = new ActBean(estimationItem1);
        estimationItem1Bean.setValue("fixedPrice", "1.0");
        estimationItem1Bean.setValue("lowQty", "2.0");
        estimationItem1Bean.setValue("lowUnitPrice", "3.0");
        estimationItem1Bean.setValue("highQty", "4.0");
        estimationItem1Bean.setValue("highUnitPrice", "5.0");
        estimationItem1Bean.save();

        Act estimation = (Act) create("act.customerEstimation");
        ActBean estimationBean = new ActBean(estimation);
        estimationBean.setValue("status", "IN_PROGRESS");
        estimationBean.addRelationship("actRelationship.customerEstimationItem", estimationItem1);

        Act estimationItem2 = (Act) create("act.customerEstimationItem");
        ActBean estimationItem2Bean = new ActBean(estimationItem2);
        estimationItem2Bean.setValue("fixedPrice", "2.0");
        estimationItem2Bean.setValue("lowQty", "3.0");
        estimationItem2Bean.setValue("lowUnitPrice", "4.0");
        estimationItem2Bean.setValue("highQty", "5.0");
        estimationItem2Bean.setValue("highUnitPrice", "6.0");
        estimationItem2Bean.save();

        estimationBean.addRelationship("actRelationship.customerEstimationItem", estimationItem2);
        estimationBean.save();

        // reload the estimation
        estimation = reload(estimation);
        estimationBean = new ActBean(estimation);

        // verify low & high totals have been calculated
        BigDecimal lowTotal = estimationBean.getBigDecimal("lowTotal");
        BigDecimal highTotal = estimationBean.getBigDecimal("highTotal");
        assertTrue(lowTotal.compareTo(BigDecimal.ZERO) > 0);
        assertTrue(highTotal.compareTo(BigDecimal.ZERO) > 0);
    }

    /**
     * Tests OVPMS-228.
     */
    @Test
    public void testOVPMS228() {
        Act act = (Act) create("act.customerAccountPayment");
        assertNotNull(act);
        ArchetypeDescriptor adesc = getArchetypeService().getArchetypeDescriptor(act.getArchetypeId());
        assertNotNull(adesc);
        NodeDescriptor ndesc = adesc.getNodeDescriptor("amount");
        assertNotNull(ndesc);
        ndesc.getValue(act);
        assertTrue(ndesc.getValue(act).getClass().getName(), ndesc.getValue(act) instanceof BigDecimal);
    }

    /**
     * Saves a collection of acts.
     */
    @Test
    public void testSaveCollection() {
        Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        Act act3 = createSimpleAct("act3", "IN_PROGRESS");

        List<IMObject> acts = Arrays.asList((IMObject) act1, act2, act3);
        checkSaveCollection(acts, 0);

        // verify the acts can be re-saved
        act1.setStatus("POSTED");
        act2.setStatus("POSTED");
        act3.setStatus("POSTED");
        checkSaveCollection(acts, 1);

        // now change the first act, and attempt to re-save the collection.
        // This should fail as the collection doesn't have the latest version
        // of act1
        act1 = reload(act1);
        act1.setStatus("COMPLETED");
        save(act1);
        try {
            checkSaveCollection(acts, 2);
            fail("Expected save to fail");
        } catch (ArchetypeServiceException expected) {
            assertEquals(FailedToSaveCollectionOfObjects, expected.getErrorCode());
        }
    }

    /**
     * Verifies that the {@link IArchetypeService#save(Collection<IMObject>)}
     * method can be used to save 2 or more acts that reference the same
     * ActRelationship.
     *
     * @throws Exception for any error
     */
    @Test
    public void testOBF163() throws Exception {
        Act estimation = (Act) create("act.customerEstimation");
        estimation.setStatus("POSTED");
        ActRelationship relationship = (ActRelationship) create("actRelationship.customerEstimationItem");
        Act item = (Act) create("act.customerEstimationItem");
        relationship.setSource(estimation.getObjectReference());
        relationship.setTarget(item.getObjectReference());
        estimation.addActRelationship(relationship);
        item.addActRelationship(relationship);

        List<Act> acts = Arrays.asList(estimation, item);
        checkSaveCollection(acts, 0);

        // reload the estimation and item. Each will have a separate copy of
        // the same persistent act relationship
        estimation = reload(estimation);
        item = reload(item);
        assertNotNull(estimation);
        assertNotNull(item);

        acts = Arrays.asList(estimation, item);

        // save the collection, and verify they have saved by checking the
        // versions.
        estimation.setTitle("changed"); // make a change to each to ensure
        item.setTitle("changed"); // they save
        checkSaveCollection(acts, 1);

        // now remove the relationship, and add a new one
        estimation.removeActRelationship(relationship);
        item.removeActRelationship(relationship);

        ActRelationship relationship2 = (ActRelationship) create("actRelationship.customerEstimationItem");
        relationship2.setSource(estimation.getObjectReference());
        relationship2.setTarget(item.getObjectReference());
        estimation.addActRelationship(relationship2);
        item.addActRelationship(relationship2);

        checkSaveCollection(acts, 2);
    }

    /**
     * Verifies that the {@link IArchetypeService#save(Collection<IMObject>)}
     * method and {@link IArchetypeService#save(IMObject) method can be used
     * to save the same object.
     *
     * @throws Exception for any error
     */
    @Test
    public void testOBF170() {
        Party person = createPerson("MR", "Jim", "Alateras");
        save(person);

        Act act1 = createSimpleAct("act1", "IN_PROGRESS");

        Participation p1 = createSimpleParticipation("act1p1", person, act1);
        act1.addParticipation(p1);

        save(act1);
        act1.setStatus("POSTED");
        Collection<Act> objects = Arrays.asList(act1);
        save(objects);

        act1.removeParticipation(p1);
        objects = Arrays.asList(act1);
        save(objects);

        save(act1);
    }

    /**
     * Verifies that related acts must be saved together i.e the entire
     * object graph must be saved.
     */
    @Test
    public void testSaveRelatedActs() {
        // Create 2 acts with the following relationship:
        // act1 -- (parent/child) --> act2
        final Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        addRelationship(act1, act2, "act1->act2", true);

        // verify that act1 cannot be saved without act2
        try {
            save(act1);
            fail("Expected save to fail");
        } catch (ArchetypeServiceException expected) {
            assertEquals(-1, act1.getId());
        }

        // verify that act2 cannot be saved without act1
        try {
            save(act2);
            fail("Expected save to fail");
        } catch (ArchetypeServiceException expected) {
            assertEquals(-1, act2.getId());
        }

        // verify both acts can be saved together
        save(Arrays.asList(act1, act2));
    }

    /**
     * Verifies that related acts can be saved individually, but in the one
     * transaction.
     */
    @Test
    public void testSaveRelatedActsInTxn() {
        // Create 2 acts with the following relationship:
        // act1 -- (parent/child) --> act2
        final Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        addRelationship(act1, act2, "act1->act2", true);

        // now try to save each act within a transaction.
        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                save(act1);
                // act has been saved, but the identifier cannot be assigned
                // until the related act is also saved
                assertEquals(-1, act1.getId());

                save(act2);

                // identifiers should have updated
                assertFalse(act1.getId() == -1);
                assertFalse(act2.getId() == -1);
                return null;
            }
        });
    }

    /**
     * Verifies that saved but uncommitted acts can be resolved using
     * {@link IArchetypeService#get(IMObjectReference)} in a transaction,
     * even if an identifier hasn't been assigned.
     */
    @Test
    public void testResolveUncommittedActs() {
        // Create 2 acts with the following relationship:
        // act1 -- (parent/child) --> act2
        final Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        addRelationship(act1, act2, "act1->act2", true);

        assertNull(get(act1.getObjectReference()));
        assertNull(get(act2.getObjectReference()));

        // now try to save each act within a transaction.
        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                save(act1);
                // act has been saved, but the identifier cannot be assigned
                // until the related act is also saved
                assertEquals(-1, act1.getId());

                // verify that the act can be retrieved by reference, and is
                // the same object
                assertSame(act1, get(act1.getObjectReference()));

                assertNull(get(act2.getObjectReference()));
                save(act2);

                // identifiers should have updated
                assertFalse(act1.getId() == -1);
                assertFalse(act2.getId() == -1);

                // verify that the acts can be retrieved by reference, and are
                // the same objects
                assertSame(act1, get(act1.getObjectReference()));
                assertSame(act2, get(act2.getObjectReference()));
                return null;
            }
        });

        IMObject reload1 = get(act1.getObjectReference());
        IMObject reload2 = get(act2.getObjectReference());
        assertNotNull(reload1);
        assertNotNull(reload2);

        // the reloaded acts should now be different objects as it is a
        // different transaction
        assertNotSame(act1, reload1);
        assertNotSame(act2, reload2);
    }

    /**
     * Verifies an act can be removed.
     */
    @Test
    public void testSingleActRemove() {
        Act act = createSimpleAct("act", "IN_PROGRESS");
        save(act);
        assertEquals(act, reload(act));

        remove(act);
        assertNull(reload(act));
    }

    /**
     * Creates a set of acts with non-parent/child relationships, and verifies
     * that deleting one act doesn't cascade to the rest.
     */
    @Test
    public void testPeerActRemoval() {
        Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        Act act3 = createSimpleAct("act3", "IN_PROGRESS");

        // create a relationship from act1 -> act2
        addRelationship(act1, act2, "act1->act2");

        // create a relationship from act2 -> act3
        addRelationship(act2, act3, "act2->act3");

        save(Arrays.asList(act1, act2, act3));

        remove(act1);
        assertNull(reload(act1));
        assertNotNull(reload(act2));
        assertNotNull(reload(act3));

        remove(act3);
        assertNull(reload(act3));
        assertNotNull(reload(act2));
    }

    /**
     * Creates a parent/child act hierarchy, and verifies that:
     * <ul>
     * <li>deleting the children doesn't affect the remaining children or
     * parent; and</li>
     * <li>deleting the parent causes deletion of the children</li>
     * </ul>
     */
    @Test
    public void testParentChildRemoval() {
        Act estimation = (Act) create("act.customerEstimation");
        estimation.setStatus("IN_PROGRESS");
        Act item1 = (Act) create("act.customerEstimationItem");
        Act item2 = (Act) create("act.customerEstimationItem");
        Act item3 = (Act) create("act.customerEstimationItem");
        ActBean bean = new ActBean(estimation);
        bean.addRelationship("actRelationship.customerEstimationItem", item1);
        bean.addRelationship("actRelationship.customerEstimationItem", item2);
        bean.addRelationship("actRelationship.customerEstimationItem", item3);
        save(Arrays.asList(estimation, item1, item2, item3));

        // remove an item, and verify it has been removed and that the other
        // acts aren't removed
        remove(item1);
        assertNull(reload(item1));
        assertNotNull(reload(estimation));
        assertNotNull(reload(item2));
        assertNotNull(reload(item3));

        // now remove the estimation and verify the remaining items are removed
        estimation = reload(estimation);
        assertNotNull(estimation);
        remove(estimation);
        assertNull(reload(estimation));
        assertNull(reload(item2));
        assertNull(reload(item3));
    }

    /**
     * Verifies that a set of acts in a cyclic parent/child relationship can
     * be removed.
     *
     * @throws Exception for any error
     */
    @Test
    public void testCyclicParentChildRemoval() throws Exception {
        // create 3 acts, with the following relationships:
        // act1 -> act2 -> act3 -> act1
        Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        Act act3 = createSimpleAct("act3", "IN_PROGRESS");

        addRelationship(act1, act2, "act1->act2", true);
        addRelationship(act2, act3, "act2->act3", true);
        addRelationship(act3, act1, "act3->act1", true);

        // remove act2. The removal should cascade to include act3 and act1
        remove(act2);
        assertNull(reload(act1));
        assertNull(reload(act2));
        assertNull(reload(act3));
    }

    /**
     * Verifies that acts with peer and parent/child relationships are handled
     * correctly at deletion, i.e the deletion cascades to those target
     * acts in parent/child relationships, and not those in peer relationships.
     *
     * @throws Exception for any error
     */
    @Test
    public void testPeerParentChildRemoval() throws Exception {
        // create 3 acts with the following relationships:
        // act1 -- (parent/child) --> act2
        //   |-------- (peer) ------> act3

        Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        Act act3 = createSimpleAct("act2", "IN_PROGRESS");

        addRelationship(act1, act2, "act1->act2", true);
        addRelationship(act1, act3, "act1->act3", false);

        save(Arrays.asList(act1, act2, act3));

        // remove act1, and verify that it and act2 are removed, and act3
        // remains.
        remove(act1);

        assertNull(reload(act1));
        assertNull(reload(act2));
        assertNotNull(reload(act3));
    }

    /**
     * Verifies that removal of acts with a parent/child relationship fails
     * when the parent act has changed subsequent to the version being deleted.
     *
     * @throws Exception for any error
     */
    @Test
    public void testStaleParentChildRemoval() throws Exception {
        Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        Act act3 = createSimpleAct("act3", "IN_PROGRESS");

        addRelationship(act1, act2, "act1->act2", true);
        save(Arrays.asList(act1, act2, act3));

        Act stale = reload(act1);

        addRelationship(act1, act3, "act1->act3", true);
        save(act1);
        save(act3);

        try {
            remove(stale);
            fail("Expected removal to fail");
        } catch (ArchetypeServiceException expected) {
            assertEquals(FailedToDeleteObject, expected.getErrorCode());
            IMObjectDAOException cause = (IMObjectDAOException) expected.getCause();

            // verify the cause comes from the DAO collection deletion method
            assertEquals(IMObjectDAOException.ErrorCode.FailedToDeleteIMObject, cause.getErrorCode());
        }
    }

    /**
     * Creates a set of acts with non-parent/child relationships, and verifies
     * that deleting one act doesn't cascade to the rest, within transactions.
     *
     * @throws Exception for any error
     */
    @Test
    public void testPeerActRemovalInTxn() throws Exception {
        final Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        final Act act3 = createSimpleAct("act3", "IN_PROGRESS");

        // create a relationship from act1 -> act2
        final ActRelationship relAct1Act2 = addRelationship(act1, act2, "act1->act2");

        // create a relationship from act2 -> act3
        final ActRelationship relAct2Act3 = addRelationship(act2, act3, "act2->act3");

        // create a relationship from act1 -> act3
        final ActRelationship relAct1Act3 = addRelationship(act1, act3, "act1->act3");

        save(Arrays.asList(act1, act2, act3));

        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                remove(act1);
                assertNull(reload(act1));

                // reload act2 and verify that it no longer has a relationship
                // to act1, and can be saved again
                Act act2reloaded = reload(act2);
                Set<ActRelationship> relationships = act2reloaded.getActRelationships();
                assertFalse(relationships.contains(relAct1Act2));
                assertTrue(relationships.contains(relAct2Act3));
                act2reloaded.setStatus("POSTED");
                save(act2reloaded);

                // reload act3 and verify that it no longer has a relationship
                // to act1, and can be saved again
                Act act3reloaded = reload(act3);
                relationships = act3reloaded.getActRelationships();
                assertFalse(relationships.contains(relAct1Act3));
                act3reloaded.setStatus("POSTED");
                save(act3reloaded);
                return null;
            }
        });
        assertNull(reload(act1));
        assertNotNull(reload(act2));
        assertNotNull(reload(act3));

        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                Act act3reloaded = reload(act3);
                remove(act3reloaded);
                assertNull(reload(act3reloaded));

                // reload act2 and verify that it no longer has a relationship
                // to act3
                Act act2reloaded = reload(act2);
                assertFalse(act2reloaded.getActRelationships().contains(relAct2Act3));
                assertEquals("POSTED", act2reloaded.getStatus());
                return null;
            }
        });

        assertNull(reload(act3));
        assertNotNull(reload(act2));
    }

    /**
     * Creates two acts, act1 and act2 with a relationship between them.
     * In a transaction, deletes act2 and associates act1 with act3.
     */
    @Test
    public void testActReplacementInTxn() {
        final Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        final Act act3 = createSimpleAct("act3", "IN_PROGRESS");

        // create a relationship from act1 -> act2
        final ActRelationship relAct1Act2 = addRelationship(act1, act2, "act1->act2");

        save(Arrays.asList(act1, act2));
        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                remove(act2);
                Act reloaded = reload(act1);

                // relationship should be removed
                assertFalse(reloaded.getActRelationships().contains(relAct1Act2));

                // add a new relationship
                addRelationship(reloaded, act3, "act1->act3");
                save(reloaded);
                save(act3);
                return null;
            }
        });

        Act reloaded = reload(act1);
        ActBean relBean = new ActBean(reloaded);
        assertTrue(relBean.getActs().contains(act3));
    }

    /**
     * Verifies that acts with peer and parent/child relationships are handled
     * correctly at deletion in a transaction, i.e the deletion cascades to
     * those target acts in parent/child relationships, and not those in peer
     * relationships.
     */
    @Test
    public void testPeerParentChildRemovalInTxn() {
        // Create 4 acts with the following relationships:
        // act1 -- (parent/child) --> act2 -- (peer) --> act 4
        //   |---- (parent/child) --> act3 -- (peer) -----|
        final Act act1 = createSimpleAct("act1", "IN_POGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        final Act act3 = createSimpleAct("act3", "IN_PROGRESS");
        final Act act4 = createSimpleAct("act4", "IN_PROGRESS");

        addRelationship(act1, act2, "act1->act2", true);
        addRelationship(act1, act3, "act1->act3", true);
        final ActRelationship relAct2Act4 = addRelationship(act2, act4, "act2->act4", false);
        final ActRelationship relAct3Act4 = addRelationship(act3, act4, "act3->act4", false);
        save(Arrays.asList(act1, act2, act3, act4));

        assertTrue(act4.getActRelationships().contains(relAct2Act4));
        assertTrue(act4.getActRelationships().contains(relAct3Act4));

        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                remove(act1);

                // reload act4 and verify it no longer has any relationships
                Act reloaded = reload(act4);
                assertTrue(reloaded.getActRelationships().isEmpty());

                // verify it can be re-saved
                reloaded.setName("A test");
                save(reloaded);
                return null;
            }
        });

        assertNull(reload(act1)); // deletion of act1 should have cascaded to
        assertNull(reload(act2)); // act2 and act3
        assertNull(reload(act3));

        Act reloaded = reload(act4);
        assertTrue(reloaded.getActRelationships().isEmpty());
    }

    /**
     * Verifies that new objects can be subsequently saved if a rollback
     * occurs, for OBF-186.
     */
    @Test
    public void testResetNewIdsOnRollback() {
        // Create 2 acts with the following relationships:
        // act1 -- (parent/child) --> act2
        final Act act1 = createSimpleAct("act1", "IN_POGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");
        final ActRelationship rel = addRelationship(act1, act2, "act1->act2", true);

        // initial ids and versions should be unset
        assertEquals(-1, act1.getId());
        assertEquals(0, act1.getVersion());
        assertEquals(-1, act2.getId());
        assertEquals(0, act2.getVersion());
        assertEquals(-1, rel.getId());
        assertEquals(-1, rel.getSource().getId());
        assertEquals(-1, rel.getTarget().getId());
        // TODO: act relationships unversioned.

        // save objects in a transaction, and rollback. Within the transaction,
        // the objects should be assigned identifiers. After rollback, they
        // should be reset to -1.
        try {
            template.execute(new TransactionCallback<Object>() {
                public Object doInTransaction(TransactionStatus status) {
                    save(act1);
                    save(act2);
                    act1.setName("f0o");
                    save(act1);

                    // objects should have ids assigned now
                    assertFalse(-1 == act1.getId());
                    assertFalse(-1 == act2.getId());
                    assertFalse(-1 == rel.getId());
                    assertFalse(-1 == rel.getSource().getId());
                    assertFalse(-1 == rel.getTarget().getId());

                    // versions don't get updated until commit
                    assertEquals(0, act1.getVersion());
                    assertEquals(0, act2.getVersion());

                    throw new RuntimeException("Trigger rollback");
                }
            });
            fail("Expected transaction to fail");
        } catch (Exception expected) {
            // expected behaviour
        }

        // id changes should be reverted on rollback
        assertEquals(-1, act1.getId());
        assertEquals(-1, act2.getId());
        assertEquals(-1, rel.getId());
        assertEquals(-1, rel.getSource().getId());
        assertEquals(-1, rel.getTarget().getId());

        assertEquals(0, act1.getVersion());
        assertEquals(0, act2.getVersion());

        // now verify the objects can be saved. Save twice to inc version.
        for (int i = 0; i < 2; ++i) {
            try {
                template.execute(new TransactionCallback<Object>() {
                    public Object doInTransaction(TransactionStatus status) {
                        save(act1);
                        save(act2);
                        return null;
                    }
                });
            } catch (Exception error) {
                error.printStackTrace();
                fail("Expected transaction to succeed");
            }
        }

        // objects should have ids assigned now
        assertFalse(-1 == act1.getId());
        assertFalse(-1 == act2.getId());
        assertFalse(-1 == rel.getId());

        assertEquals(1, act1.getVersion());
        assertEquals(1, act2.getVersion());

        // now verfiy that a subsequent rollback of persistent objects
        // doesn't reset the ids
        try {
            template.execute(new TransactionCallback<Object>() {
                public Object doInTransaction(TransactionStatus status) {
                    save(act1);
                    save(act2);
                    throw new RuntimeException("Trigger rollback");
                }
            });
            fail("Expected transaction to fail");
        } catch (Exception expected) {
            // expected behaviour
        }

        // objects should have ids and versions assigned still
        assertFalse(-1 == act1.getId());
        assertFalse(-1 == act2.getId());
        assertFalse(-1 == rel.getId());

        assertEquals(1, act1.getVersion());
        assertEquals(1, act2.getVersion());
    }

    /**
     * Tests the fix for OBF-190.
     */
    @Test
    public void testOBF190() {
        // Create 2 acts with the following relationship:
        // act1 -- (parent/child) --> act2
        final Act act1 = createSimpleAct("act1", "IN_PROGRESS");
        final Act act2 = createSimpleAct("act2", "IN_PROGRESS");

        String name = "act1->act2";
        final ActRelationship rel = addRelationship(act1, act2, name, true);
        save(Arrays.asList(act1, act2));

        final String newName = "act1->act2 changed";

        template.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                // reload act2 and verify it contains the relationship
                Act reloaded = reload(act2);
                assertTrue(reloaded.getTargetActRelationships().contains(rel));

                // update the relationship and save act1
                rel.setName(newName);
                save(act1);
                return null;
            }
        });

        // reload act1 and verify it contains the relationship
        Act reloaded = reload(act1);
        assertNotNull(reloaded);
        Set<ActRelationship> relationships = reloaded.getSourceActRelationships();
        assertEquals(1, relationships.size());
        ActRelationship reloadedRel = relationships.toArray(new ActRelationship[relationships.size()])[0];

        // verify the relationship was updated
        assertEquals(reloadedRel, rel);
        assertEquals(reloadedRel.getVersion(), rel.getVersion());
        assertEquals(newName, reloadedRel.getName());

        // verify the acts can be saved again
        save(act1);
        save(act2);
    }

    /**
     * Sets up the test case.
     */
    @Before
    public void setUp() {
        PlatformTransactionManager txnManager = (PlatformTransactionManager) applicationContext
                .getBean("txnManager");
        template = new TransactionTemplate(txnManager);
    }

    /**
     * Saves a collection via the {@link IMObjectDAOHibernate#save(Collection)}
     * method and verifies they have saved with the correct version.
     *
     * @param objects the objects to save
     * @param version the expected version
     */
    private void checkSaveCollection(List<? extends IMObject> objects, long version) {
        save(objects);
        for (IMObject object : objects) {
            assertEquals(version, object.getVersion());
            IMObject reloaded = reload(object);
            assertEquals(object, reloaded);
            assertEquals(version, reloaded.getVersion());
        }
    }

    /**
     * Helper to reload an object.
     *
     * @param object the object to reload
     * @return the reloaded object, or <tt>null</tt> if it can't be found
     * @throws ArchetypeServiceException for any error
     */
    @SuppressWarnings("unchecked")
    private <T extends IMObject> T reload(T object) {
        return (T) get(object.getObjectReference());
    }

    /**
     * Create a simple act
     *
     * @param name   the name of the act
     * @param status the status of the act
     * @return Act
     */
    private Act createSimpleAct(String name, String status) {
        Act act = (Act) create("act.simple");

        act.setName(name);
        act.setStatus(status);
        act.setActivityStartTime(new Date());
        act.setActivityEndTime(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000));

        return act;
    }

    /**
     * Create a simple participation.
     *
     * @param name   the name of the participation
     * @param entity the entity in the participation
     * @param act    the act in the participation
     * @return a new participation
     */
    private Participation createSimpleParticipation(String name, Entity entity, Act act) {
        Participation participation = (Participation) create("participation.simple");
        participation.setName(name);
        participation.setEntity(entity.getObjectReference());
        participation.setAct(act.getObjectReference());

        return participation;
    }

    /**
     * Create a person with the specified title, firstName and lastName.
     *
     * @param title     the person's title
     * @param firstName the person's first name
     * @param lastName  the person's last name
     * @return a new party
     */
    private Party createPerson(String title, String firstName, String lastName) {
        Party person = (Party) create("party.person");
        person.getDetails().put("lastName", lastName);
        person.getDetails().put("firstName", firstName);
        person.getDetails().put("title", title);

        return person;
    }

    /**
     * Helper to add a peer <em>actRelationship.simple</em> relationship
     * between two acts.
     *
     * @param source the source act
     * @param target that target act
     * @param name   the relationship name
     * @return the relationship
     */
    private ActRelationship addRelationship(Act source, Act target, String name) {
        return addRelationship(source, target, name, false);
    }

    /**
     * Helper to add an <em>actRelationship.simple</em> relationship between two
     * acts.
     *
     * @param source      the source act
     * @param target      that target act
     * @param name        the relationship name
     * @param parentChild if <tt>true</tt> add a parent-child relationship,
     *                    otherwise add a peer relationship
     * @return the relationship
     */
    private ActRelationship addRelationship(Act source, Act target, String name, boolean parentChild) {
        ActBean bean = new ActBean(source);
        ActRelationship result = bean.addRelationship("actRelationship.simple", target);
        result.setName(name);
        result.setParentChildRelationship(parentChild);
        return result;
    }
}