gr.interamerican.bo2.impl.open.hibernate.TestScenarioInvoiceHibernateOperations.java Source code

Java tutorial

Introduction

Here is the source code for gr.interamerican.bo2.impl.open.hibernate.TestScenarioInvoiceHibernateOperations.java

Source

/*******************************************************************************
 * Copyright (c) 2013 INTERAMERICAN PROPERTY AND CASUALTY INSURANCE COMPANY S.A. 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/copyleft/lesser.html
 * 
 * This 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 Lesser General Public License for more details.
 ******************************************************************************/
package gr.interamerican.bo2.impl.open.hibernate;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import gr.interamerican.bo2.arch.DetachStrategy;
import gr.interamerican.bo2.arch.PersistenceWorker;
import gr.interamerican.bo2.arch.PersistentObject;
import gr.interamerican.bo2.arch.exceptions.DataException;
import gr.interamerican.bo2.arch.exceptions.InitializationException;
import gr.interamerican.bo2.arch.exceptions.LogicException;
import gr.interamerican.bo2.arch.exceptions.PoNotFoundException;
import gr.interamerican.bo2.arch.exceptions.UnexpectedException;
import gr.interamerican.bo2.impl.open.annotations.ManagerName;
import gr.interamerican.bo2.impl.open.annotations.Parameter;
import gr.interamerican.bo2.impl.open.creation.Factory;
import gr.interamerican.bo2.impl.open.po.PoUtils;
import gr.interamerican.bo2.impl.open.runtime.AbstractBo2RuntimeCmd;
import gr.interamerican.bo2.impl.open.runtime.Execute;
import gr.interamerican.bo2.impl.open.workers.AbstractPersistenceOperation;
import gr.interamerican.bo2.test.def.posamples.Customer;
import gr.interamerican.bo2.test.def.posamples.Invoice;
import gr.interamerican.bo2.test.def.posamples.InvoiceCustomer;
import gr.interamerican.bo2.test.def.posamples.InvoiceLine;
import gr.interamerican.bo2.test.def.posamples.InvoiceRule;
import gr.interamerican.bo2.test.def.posamples.InvoiceSubLine;
import gr.interamerican.bo2.test.def.posamples.InvoiceSubRule;
import gr.interamerican.bo2.test.def.posamples.SamplesFactory;
import gr.interamerican.bo2.test.scenarios.DeleteInvoiceData;
import gr.interamerican.bo2.test.utils.UtilityForBo2Test;
import gr.interamerican.bo2.utils.DateUtils;
import gr.interamerican.bo2.utils.Debug;
import gr.interamerican.bo2.utils.annotations.Child;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.Set;

import org.hibernate.Hibernate;
import org.hibernate.LazyInitializationException;
import org.hibernate.LockOptions;
import org.hibernate.ReplicationMode;
import org.hibernate.Session;
import org.hibernate.proxy.HibernateProxy;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * This class tests operations of {@link GenericHibernatePersistenceWorker}
 * against a set of {@link PersistentObject} entities that have mainly 
 * composite keys.
 * 
 * The keys of child elements of the persistent objects contain the properties
 * of their parent's key.
 * 
 * TODO: Used an alternate factory with mapped implementations. Why???
 */
public class TestScenarioInvoiceHibernateOperations {

    /**
     * samples factory
     */
    private static SamplesFactory factory = SamplesFactory.getBo2Factory();

    /**
     * Clear operation.
     */
    private DeleteInvoiceData clear;

    /**
     * Invoice being currently tested.
     */
    private Invoice invoice;

    /**
     * Invoice copy.
     */
    private Invoice copy;

    /**
     * InvoiceCustomer (one-to-one with Invoice).
     */
    private InvoiceCustomer iCust;

    /**
     * Customer (many-to-one with InvoiceCustomer).
     */
    private Customer customer;

    /**
     * Customer no.
     */
    private String customerNo = "C0"; //$NON-NLS-1$

    /**
     * Customer taxId.
     */
    private String taxId = "taxId0"; //$NON-NLS-1$

    /**
     * invoiceNo
     */
    String invoiceNo = "AA2"; //$NON-NLS-1$

    /**
     * copy invoiceNo
     */
    String copyInvoiceNo = "BB2"; //$NON-NLS-1$

    /**
     * Creates a new TestScenarioInvoiceHibernateOperations object. 
     */
    public TestScenarioInvoiceHibernateOperations() {
        super();
        clear = new DeleteInvoiceData();
    }

    /**
     * before each test.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Before
    public void beforeEachTest() throws DataException, LogicException, UnexpectedException {
        Execute.transactional(clear);
        Debug.setActiveModule(this);
    }

    /**
     * after each test.
     */
    @After
    public void afterEachTest() {
        Debug.resetActiveModule();
    }

    /**
     * Test case for checking store and read.
     * 
     * <li>store
     * <li>compare initial object with saved version. 
     * <li>commit
     * <li>read
     * <li>compare initial object with read version.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testStoreAndRead() throws DataException, LogicException, UnexpectedException {
        final String userid = "X"; //$NON-NLS-1$
        UtilityForBo2Test.setCurrentUser(userid);

        /* Store test scenario */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
                assertTrue(PoUtils.deepEquals(invoice, po));

                assertEquals(userid, po.getLastModifiedBy());
                for (InvoiceLine line : po.getLines()) {
                    assertEquals(userid, line.getLastModifiedBy());
                    for (InvoiceSubLine subline : line.getSubLines()) {
                        assertEquals(userid, subline.getLastModifiedBy());
                    }
                }
            }
        };

        /* Read after store test scenario */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.read(po);
                assertTrue(PoUtils.deepEquals(invoice, po));
            }
        };

        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        invoice.tidy();

        readOp.setPo(invoice);
        Execute.transactional(readOp);
    }

    /**
     * Test case for checking that collections are indeed lazily initialized.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test(expected = LazyInitializationException.class)
    public void testRead_LazyInitializationOfCollections()
            throws DataException, LogicException, UnexpectedException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Now read them */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /* 
         * now attempt to read elements of a proxied set. Note that this
         * will throw an exception even if it is executed in a transaction.
         */
        Set<InvoiceLine> lines = invoice.getLines();
        for (InvoiceLine line : lines) {
            assertNotNull(line.getSubLines());
            assertFalse(Hibernate.isInitialized(line.getSubLines()));
            line.getSubLines().size(); // throws LazyInitializationException
        }
    }

    /**
     * Test case for checking that one-to-one associations are indeed
     * lazily initialized.
     * 
     * The only way to lazily initialize a one-to-one association object
     * is to specify lazy="proxy" AND constrained="true" in the mapping.
     * The latter means that the object cannot be null (which is the case
     * for {@link Child} POs). If we do not specify constrained="true" hibernate
     * does not know whether to put a null value or a proxy, so it checks
     * the database and therefore populates the object anyway. 
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testRead_OneToOne() throws DataException, LogicException, UnexpectedException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Read InvoiceSubLine scenario */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /*
         * Eager fetch of one-to-one
         */
        InvoiceCustomer cust = invoice.getCustomer();
        assertNotNull(cust);
        assertTrue(Hibernate.isInitialized(cust));
        cust.getRoleId();
    }

    /**
     * Test case for checking delete.
     * 
     * <li>store (implicitly reads)
     * <li>delete
     * <li>read (expecting PoNotFoundException)
     * <li>transaction gets rolled back
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test(expected = PoNotFoundException.class)
    public void testDelete_Fails() throws DataException, LogicException, UnexpectedException {

        /* Store and then delete. Expecting read to throw PoNotFound */
        AbstractPersistenceOperation<Invoice> test1 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
                po = pw.delete(po);
                /* throws PoNotFoundException, transaction gets rolled back. */
                po = pw.read(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        test1.setPo(invoice);
        Execute.transactional(test1);
    }

    /**
     * Test case for checking delete.
     * 
     * <li>store
     * <li>commit
     * <li>read
     * <li>delete
     * <li>commit
     * <li>read
     * <li>catch the PoNotFoundException
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testDelete_Succeeds() throws DataException, LogicException, UnexpectedException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* read and delete */
        AbstractPersistenceOperation<Invoice> deleteOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.read(po);
                po = pw.delete(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        deleteOp.setPo(invoice);
        Execute.transactional(deleteOp);

        /* confirm */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                boolean caught = false;
                try {
                    po = pw.read(po);
                } catch (PoNotFoundException pnfe) {
                    assertNotNull(pnfe);
                    caught = true;
                } finally {
                    assertTrue(caught);
                }
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);
    }

    /**
     * Test case for checking update.
     * 
     * <li>store
     * <li>read
     * <li>update
     * <li>read
     * <li>commit
     * <li>read
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    @SuppressWarnings("nls")
    public void testUpdate() throws DataException, LogicException, UnexpectedException {

        Debug.setActiveModule(this);

        /* Store,read,add line,update,remove 2 lines, remove 1 subLine, read test scenario */
        AbstractPersistenceOperation<Invoice> test1 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* store */
                po = pw.store(po);

                /* compare initial with stored */
                assertTrue(PoUtils.deepEquals(invoice, po));

                /* add one line */
                po.setInvoiceDate(DateUtils.today());
                InvoiceLine line5 = factory.sampleInvoiceLine(5);
                po.getLines().add(line5);
                invoice = po;

                /* update */
                po = pw.update(po);

                /* compare updated with initial */
                assertTrue(PoUtils.deepEquals(invoice, po));

                /* read */
                po = pw.read(po);

                /* compare read with initial */
                assertTrue(PoUtils.deepEquals(invoice, po));
                assertEquals(5, po.getLines().size());

                /* remove two lines */
                InvoiceLine line1 = factory.sampleInvoiceLine(1);
                line1.setInvoiceNo(po.getInvoiceNo());
                po.getLines().remove(line1);
                po.getLines().remove(line5);
                assertEquals(3, po.getLines().size());
                invoice = po;

                /* update */
                po = pw.update(po);

                /* compare updated with initial */
                assertTrue(PoUtils.deepEquals(invoice, po));

                /* remove a subLine */
                InvoiceSubLine sLine = factory.sampleInvoiceSubLine(2);
                sLine.setInvoiceNo(invoiceNo);
                sLine.setLineNo(2);
                for (InvoiceLine line : po.getLines()) {
                    if (line.getLineNo() == 2) {
                        System.err.println("removed sline");
                        line.getSubLines().remove(sLine);
                        assertEquals(0, line.getSubLines().size());
                    }
                }
                invoice = po;
                po = pw.update(po);

                assertTrue(PoUtils.deepEquals(invoice, po));

                for (InvoiceLine line : po.getLines()) {
                    if (line.getLineNo() == 2) {
                        System.err.println("removed sline");
                        line.getSubLines().remove(sLine);
                        assertEquals(0, line.getSubLines().size());
                    }
                }
                /* read */
                po = pw.read(po);

                /* compare read with initial */
                assertTrue(PoUtils.deepEquals(invoice, po));
                assertEquals(3, po.getLines().size());
                invoice = null;
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        test1.setPo(invoice);
        Execute.transactional(clear, test1);
    }

    /**
     * Tests many to one relationships and deep-copy on POs that
     * have many-to-one relationships.
     * 
     * @throws LogicException 
     * @throws DataException 
     * @throws UnexpectedException 
     * 
     */
    @SuppressWarnings("nls")
    @Test
    public void testManyToOne() throws DataException, LogicException, UnexpectedException {

        /* store the samples, store a customer and setup the sample invoice with him */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.store(invoice);

                Customer cust = factory.sampleCustomer("tid0");
                cust.setCustomerNo("cust001");
                PersistenceWorker<Customer> cpw = openPw(Customer.class);
                cust = cpw.store(cust);

                invoice.getCustomer().setCustomer(cust);
                ipw.update(invoice);
                invoice = null;
            }
        }.execute();

        /* read the invoice and get the invoiceCustomer */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.read(invoice);
                invoice.getCustomer();
            }
        }.execute();

        /* read the customer - a LazyInitializationException is thrown
         * The exception would be thrown, even if the following code was
         * running inside an active transaction.
         */
        boolean caught = false;
        try {
            invoice.getCustomer().getCustomer().getCustomerName();
        } catch (LazyInitializationException lie) {
            assertNotNull(lie);
            caught = true;
        } finally {
            assertTrue(caught);
        }

        /* deep copy invoice */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.read(invoice);

                copy = PoUtils.deepCopy(invoice);
                assertTrue(PoUtils.deepEquals(invoice, copy));

                copy.setInvoiceNo(copyInvoiceNo);
                copy = ipw.store(copy);
            }
        }.execute();
    }

    /**
     * Tests duplicating a PO with PoUtils.deepCopy 
     * 
     * Note that copy MUST be performed in the same transaction
     * as the reading of the original. If this is not the case,
     * the method will attempt to initialize proxies of a detached
     * object, which throws an Exception.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    @SuppressWarnings("nls")
    public void testReadCopyStore() throws DataException, LogicException, UnexpectedException {

        UtilityForBo2Test.setCurrentUser("who"); //current user.

        /* Store test scenario */
        AbstractPersistenceOperation<Invoice> test1 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceDate(new Date());
        InvoiceLine line = Factory.create(InvoiceLine.class);
        line.setLineNo(1);
        line.setAmount(100.d);
        invoice.getLines().add(line);
        invoice.setInvoiceNo(invoiceNo);
        InvoiceCustomer iCustomer = Factory.create(InvoiceCustomer.class);
        iCustomer.setRoleId(1);
        invoice.setCustomer(iCustomer);
        test1.setPo(invoice);
        Execute.transactional(test1);

        /* Read after store and copy */
        AbstractPersistenceOperation<Invoice> test2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.read(po);
                invoice = po;
                copy = PoUtils.deepCopy(po);
            }
        };
        test2.setPo(invoice);
        Execute.transactional(test2);
        assertNotNull(copy);
        assertTrue(PoUtils.deepEquals(copy, invoice));

        /* store copy test scenario */
        AbstractPersistenceOperation<Invoice> test3 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                copy = pw.store(po);
            }
        };
        copy.setInvoiceNo(copyInvoiceNo);

        test3.setPo(copy);
        Execute.transactional(test3);
        assertNotNull(copy);
        assertFalse(copy.getKey().equals(invoice.getKey()));

        /* delete copy */
        AbstractPersistenceOperation<Invoice> test4 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.delete(po);
            }
        };
        test4.setPo(copy);
        Execute.transactional(test4);
        invoice = null;
    }

    /**
     * Tests re-attaching a detached UNMODIFIED instance.
     * 
     * <li>transaction1: store
     * <li>transaction2: read
     * <li>transaction3: re-attach, modify and persist
     * <li>transaction4: confirm operations
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     */
    @Test
    public void testReattachToSession_unmodifiedInstance()
            throws UnexpectedException, DataException, LogicException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Now read them */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with lock()
                 * This will only work if the detached instances are unmodified.
                 * session.update() has the same effect, but is unnecessary.
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    session.buildLockRequest(LockOptions.NONE).lock(invoice);
                } catch (InitializationException e) {
                    fail(e.toString());
                }

                /* add a subLine to each line. Note that subLines are proxied */
                Set<InvoiceLine> lines = invoice.getLines();
                for (InvoiceLine line : lines) {
                    assertNotNull(line.getSubLines());
                    assertFalse(Hibernate.isInitialized(line.getSubLines()));
                    line.getSubLines().add(factory.sampleInvoiceSubLine(line.getLineNo() + 1));
                }

                /* persist the updated invoice */
                invoice = pw.update(invoice);
            }
        };
        updateOp.setPo(invoice);
        Execute.transactional(updateOp);

        /* confirm results, now each line has 2 subLines */
        AbstractPersistenceOperation<Invoice> readOp2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                Set<InvoiceLine> lines = invoice.getLines();
                for (InvoiceLine line : lines) {
                    assertTrue(line.getSubLines().size() == 2);
                }
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp2.setPo(invoice);
        Execute.transactional(readOp2);
    }

    /**
     * Tests re-attaching an object graph containing uninitialized HibernateProxy instances
     * after serializing and de-serializing it with the standard Java serialization
     * mechanism. After re-attaching the proxies are initialized.
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     * @throws IOException 
     * @throws ClassNotFoundException 
     */
    @Test
    public void testReattachToSession_deSerializedInstance()
            throws UnexpectedException, DataException, LogicException, IOException, ClassNotFoundException {

        /* first, store the samples - including a Customer */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                customer = factory.sampleCustomer(taxId);
                customer.setCustomerNo(customerNo);
                PersistenceWorker<Customer> cpw = openPw(Customer.class);
                cpw.store(customer);

                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                invoice.getCustomer().setCustomer(customer);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                ipw.store(invoice);
            }
        }.execute();

        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                invoice = openPw(Invoice.class).read(invoice);
            }
        }.execute();
        Customer c = invoice.getCustomer().getCustomer();
        Assert.assertTrue(c instanceof HibernateProxy);
        Assert.assertTrue(((HibernateProxy) c).getHibernateLazyInitializer().isUninitialized());

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(invoice);
        oos.flush();
        baos.flush();
        oos.close();
        baos.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        invoice = (Invoice) ois.readObject();

        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                PoUtils.reattach(invoice, getProvider());
                Assert.assertEquals(taxId, invoice.getCustomer().getCustomer().getTaxId());
            }
        }.execute();

    }

    /**
     * Tests re-attaching an object graph of a deep copied object. Many-to-one associations
     * should be reattached for reading.
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     */
    @Test
    public void testReattachToSession_deepCopiedInstance()
            throws UnexpectedException, DataException, LogicException {

        /* first, store the samples - including a Customer */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                customer = factory.sampleCustomer(taxId);
                customer.setCustomerNo(customerNo);
                PersistenceWorker<Customer> cpw = openPw(Customer.class);
                cpw.store(customer);

                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                invoice.getCustomer().setCustomer(customer);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                ipw.store(invoice);
            }
        }.execute();

        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                invoice = openPw(Invoice.class).read(invoice);
                invoice = PoUtils.deepCopy(invoice);
            }
        }.execute();

        Customer c = invoice.getCustomer().getCustomer();
        Assert.assertNull(invoice.getLastModified());
        Assert.assertTrue(HibernateBo2Utils.isTransient(invoice));
        Assert.assertTrue(c instanceof HibernateProxy);
        Assert.assertTrue(((HibernateProxy) c).getHibernateLazyInitializer().isUninitialized());

        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                PoUtils.reattach(invoice, getProvider());
                Assert.assertEquals(taxId, invoice.getCustomer().getCustomer().getTaxId());
            }
        }.execute();

    }

    /**
     * Tests re-attaching a detached MODIFIED instance.
     * 
     * <li>transaction1: store
     * <li>transaction2: read
     * <li>transaction3: modify, re-attach, modify and persist
     * <li>transaction4: confirm operations
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     */
    @Test
    public void testReattachToSession_modifiedInstance() throws UnexpectedException, DataException, LogicException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Now read them */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                invoice.getLines().size();
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /* add a new line with a subLine */
        InvoiceLine newLine = factory.sampleInvoiceLine(5);
        newLine.getSubLines().add(factory.sampleInvoiceSubLine(newLine.getLineNo()));
        invoice.getLines().add(newLine);

        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with session.update()
                 * merge() has the same effect, but we call merge in worker.update() anyway.
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    session.update(invoice);
                } catch (InitializationException e) {
                    fail(e.toString());
                }

                /* add a subLine to each line. */
                Set<InvoiceLine> lines = invoice.getLines();
                assertTrue(lines.size() == 5);
                for (InvoiceLine line : lines) {
                    assertNotNull(line.getSubLines());
                    line.getSubLines().add(factory.sampleInvoiceSubLine(line.getLineNo() + 1));
                }

                /* persist the updated invoice */
                invoice = pw.update(invoice);
            }
        };
        updateOp.setPo(invoice);
        Execute.transactional(updateOp);

        /* confirm results, now each line has 2 subLines */
        AbstractPersistenceOperation<Invoice> readOp2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                Set<InvoiceLine> lines = invoice.getLines();
                assertTrue(lines.size() == 5);
                for (InvoiceLine line : lines) {
                    assertTrue(line.getSubLines().size() == 2);
                }
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp2.setPo(invoice);
        Execute.transactional(readOp2);
    }

    /**
     * Tests re-attaching a detached MODIFIED instance and then flushing.
     * The modifications that the object had before the updated are persisted
     * on flush.
     * 
     * <li>transaction1: store
     * <li>transaction2: read
     * <li>transaction3: modify, re-attach, flush
     * <li>transaction4: confirm operations
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     */
    @Test
    public void testReattachToSession_modifiedInstanceAndFlushManually()
            throws UnexpectedException, DataException, LogicException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Now read them */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                invoice.getLines().size();
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /* add a new line with a subLine */
        InvoiceLine newLine = factory.sampleInvoiceLine(5);
        newLine.getSubLines().add(factory.sampleInvoiceSubLine(newLine.getLineNo()));
        invoice.getLines().add(newLine);

        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with session.update()
                 * and flush() manually (call tidy() first to fix keys).
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    session.update(invoice);
                    invoice.tidy();
                    session.flush();
                } catch (InitializationException e) {
                    fail(e.toString());
                }
            }
        };
        updateOp.setPo(invoice);
        Execute.transactional(updateOp);

        /* confirm results, now each line has 1 subLine */
        AbstractPersistenceOperation<Invoice> readOp2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                Set<InvoiceLine> lines = invoice.getLines();
                assertTrue(lines.size() == 5);
                for (InvoiceLine line : lines) {
                    assertTrue(line.getSubLines().size() == 1);
                }
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp2.setPo(invoice);
        Execute.transactional(readOp2);
    }

    /**
     * Tests evicting an attached instance.
     * 
     * <li>transaction1: store
     * <li>transaction2: read
     * <li>transaction3: re-attach, modify, evict and persist
     * <li>transaction4: confirm operations
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testEvictingFromSession() throws DataException, LogicException, UnexpectedException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);
        invoice = storeOp.getPo();
        copy = factory.sampleInvoiceFull(4);
        copy.setInvoiceNo(copyInvoiceNo);
        storeOp.setPo(copy);
        Execute.transactional(storeOp);
        copy = storeOp.getPo();

        /* re-attach to session, modify and update */
        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with lock()
                 * This will only work if the detached instances are unmodified.
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    session.buildLockRequest(LockOptions.NONE).lock(invoice);
                    session.buildLockRequest(LockOptions.NONE).lock(copy);
                } catch (InitializationException e) {
                    fail(e.toString());
                }

                /* now that the objects are re-attached I can modify them */
                invoice.getLines().add(factory.sampleInvoiceLine(5));
                copy.getLines().add(factory.sampleInvoiceLine(5));

                /*
                 * evicting copy. Even though I will only explicitly update invoice, flush() will
                 * also send SQL for the modifications of copy to the connection. If I do not want
                 * to persist the modifications of copy, I have to evict it from the session.
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    session.evict(copy);
                } catch (InitializationException e) {
                    fail(e.toString());
                }

                invoice = pw.update(invoice);
            }
        };
        Execute.transactional(updateOp);

        /* confirm results, now invoice has 5 lines and copy only 4 */
        AbstractPersistenceOperation<Invoice> readOp2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.read(po);
                po.getLines().size();
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp2.setPo(invoice);
        Execute.transactional(readOp2);
        invoice = readOp2.getPo();
        assertTrue(invoice.getLines().size() == 5);

        copy = Factory.create(Invoice.class);
        copy.setInvoiceNo(copyInvoiceNo);
        readOp2.setPo(copy);
        Execute.transactional(readOp2);
        copy = readOp2.getPo();
        assertTrue(copy.getLines().size() == 4);
    }

    /**
     * Tests that when re-attaching a detached instance other references of
     * objects owned by this instance to be re-attached as well.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testReattachInvoiceReattachesChildren() throws DataException, LogicException, UnexpectedException {

        /* first, store the samples - including a Customer */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                customer = factory.sampleCustomer(taxId);
                customer.setCustomerNo(customerNo);
                PersistenceWorker<Customer> cpw = openPw(Customer.class);
                cpw.store(customer);

                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                invoice.getCustomer().setCustomer(customer);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                ipw.store(invoice);
            }
        }.execute();

        /* Now read the invoice and keep a reference to the InvoiceCustomer and the Customer. */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                iCust = invoice.getCustomer();
                customer = iCust.getCustomer();
                Assert.assertNotNull(iCust);
                Assert.assertNotNull(customer);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /* re-attach to session, check that the InvoiceCustomer and customer
           references are consistent with the re-attached Invoice. */
        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with session.update()
                 */
                try {
                    HibernateSessionProvider hib = getProvider().getResource("LOCALDB", //$NON-NLS-1$
                            HibernateSessionProvider.class);
                    Session session = hib.getHibernateSession();
                    session.update(invoice);
                    Assert.assertTrue(session.contains(iCust));
                    Assert.assertTrue(session.contains(customer));
                } catch (InitializationException e) {
                    fail(e.toString());
                }

                Assert.assertSame(iCust, invoice.getCustomer());
                Assert.assertSame(customer, invoice.getCustomer().getCustomer());
            }
        };
        updateOp.setPo(invoice);
        Execute.transactional(updateOp);

    }

    /**
     * Tests that when re-attaching a detached instance transient objects
     * on its graph are not attached.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testReattachInvoiceDoesNotAttachTransientChildren()
            throws DataException, LogicException, UnexpectedException {

        /* first, store the samples - including a Customer */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.store(invoice);
                invoice.getLines().size(); //force init persistent collection
            }
        }.execute();

        /* now add an invoiceline and reattach */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                InvoiceLine line = factory.sampleInvoiceLine(5);
                invoice.getLines().add(line);
                PoUtils.reattach(invoice, getProvider());
                Assert.assertNull(line.getLastModified());
            }
        }.execute();

        /* now perform an update */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                /*
                 * INFAMOUS NONTHREADSAFEACCESS TO SESSION ASSERTION ERROR
                 * The following line, if uncommented causes the above error.
                 * The reason is that transient instances are attached and 
                 * then evicted from the session. When the worker update
                 * is performed they are attached again. Hibernate then throws
                 * an AssertionError on flush(), because, this is a fishy
                 * behavior. There is no reason to reattach before a worker
                 * update since Bo2 2.0.0061.
                 */
                //            PoUtils.reattach(invoice, getProvider()); 
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                Invoice i = ipw.update(invoice);
                Assert.assertTrue(i == invoice);
            }
        }.execute();

    }

    /**
     * Tests that when re-attaching a detached instance transient objects
     * on its graph are not attached.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testReattachForUpdateInvoiceAttachesTransientChildren()
            throws DataException, LogicException, UnexpectedException {

        /* first, store the samples */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.store(invoice);
                invoice.getLines().size(); //force init persistent collection
            }
        }.execute();

        /* now add an invoiceline and reattach for update */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                InvoiceLine line = factory.sampleInvoiceLine(5);
                invoice.getLines().add(line);
                PoUtils.reattachForUpdate(invoice, getProvider());
                Assert.assertNotNull(line.getLastModified()); //attached

                /* perform the db update in this UoW */
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                Invoice i = ipw.update(invoice);
                Assert.assertTrue(i == invoice);
            }
        }.execute();

    }

    /**
     * Tests that when re-attaching a detached instance transient objects
     * on its graph are not attached.
     * 
     * @throws DataException
     * @throws LogicException
     * @throws UnexpectedException 
     */
    @Test
    public void testDetachAfterReattachForUpdateInvoiceLeavesTransientChildrenDetached()
            throws DataException, LogicException, UnexpectedException {

        /* first, store the samples */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = factory.sampleInvoiceFull(4);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.store(invoice);
                invoice.getLines().size(); //force init persistent collection
            }
        }.execute();

        /* now add an invoiceline with a subline and reattach for update */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                InvoiceLine line = factory.sampleInvoiceLine(5);
                InvoiceSubLine sLine = factory.sampleInvoiceSubLine(1);
                line.getSubLines().add(sLine);
                invoice.getLines().add(line);

                PoUtils.reattachForUpdate(invoice, getProvider());
                Assert.assertNotNull(line.getLastModified()); //attached
                Assert.assertNotNull(sLine.getLastModified()); //attached

                DetachStrategy ds = PoUtils.getDetachStrategy(invoice);
                ds.detach(invoice, getProvider());
                Assert.assertNull(line.getLastModified()); //detached
                Assert.assertNull(sLine.getLastModified()); //detached
            }
        }.execute();

    }

    /*---------------------------------------------------------------------------
      Advanced test cases.
     ----------------------------------------------------------------------------*/

    /**
     * Temporary alias for a rule.
     */
    private InvoiceRule ruleRef;

    /**
     * This test case tests that after re-attaching an object possible
     * occurrences of many-to-one associations in the object graph mapped
     * with insert="false" update="false" are also re-attached.
     * 
     * @throws LogicException 
     * @throws DataException 
     * @throws UnexpectedException 
     * 
     */
    @Test
    public void testReattachmentOfObjectsThroughManyToOne()
            throws UnexpectedException, DataException, LogicException {
        /* 1. store an invoice with a line, a subline, a rule and a subrule */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = factory.sampleInvoice(invoiceNo);
                InvoiceSubLine sLine = factory.sampleInvoiceSubLine(1);
                InvoiceLine line = factory.sampleInvoiceLine(1);
                InvoiceSubRule sRule = factory.sampleInvoiceSubRule(1L);
                InvoiceRule rule = factory.sampleInvoiceRule(1L);
                iCust = factory.sampleInvoiceCustomer();

                rule.getSubRules().add(sRule);
                line.getSubLines().add(sLine);
                invoice.getRules().add(rule);
                invoice.getLines().add(line);
                invoice.setCustomer(iCust);

                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                ipw.store(invoice);
            }
        }.execute();

        /* 2. read that invoice and connect the subline to the rule */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.read(invoice);
                InvoiceRule rule = invoice.getRules().iterator().next();
                InvoiceSubLine sLine = invoice.getLines().iterator().next().getSubLines().iterator().next();
                sLine.setRule(rule);
                ipw.update(invoice);
            }
        }.execute();

        /* 3. read that invoice again */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.read(invoice);
                ruleRef = invoice.getRules().iterator().next();
            }
        }.execute();

        /* 4. re-attach that invoice and test getting the rule and the subrules from the sline */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                PoUtils.reattach(invoice, getProvider());
                InvoiceSubLine sLine = invoice.getLines().iterator().next().getSubLines().iterator().next();
                InvoiceRule rule = sLine.getRule();

                Assert.assertTrue(rule == ruleRef);
                Assert.assertEquals(rule, ruleRef);
                Assert.assertSame(rule, ruleRef);
            }
        }.execute();

        /* 5. read that invoice again */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.read(invoice);
            }
        }.execute();

        /* 6. reattach  and check that we can get to the subrules via a subline */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                PoUtils.reattach(invoice, getProvider());
                InvoiceSubLine sLine = invoice.getLines().iterator().next().getSubLines().iterator().next();
                InvoiceRule rule = sLine.getRule();
                rule.getSubRules().iterator().next();
            }
        }.execute();

    }

    /**
     * This test case is the same as the one above. The only
     * difference is that the Invoice is read using an HQL query.
     * 
     * @throws LogicException 
     * @throws DataException 
     * @throws UnexpectedException 
     * 
     */
    @Test
    public void testReattachmentOfObjectsFromHqlThroughManyToOne()
            throws UnexpectedException, DataException, LogicException {
        /* 1. store an invoice with a line, a subline, a rule and a subrule */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = factory.sampleInvoice(invoiceNo);
                InvoiceSubLine sLine = factory.sampleInvoiceSubLine(1);
                InvoiceLine line = factory.sampleInvoiceLine(1);
                InvoiceSubRule sRule = factory.sampleInvoiceSubRule(1L);
                InvoiceRule rule = factory.sampleInvoiceRule(1L);
                iCust = factory.sampleInvoiceCustomer();

                rule.getSubRules().add(sRule);
                line.getSubLines().add(sLine);
                invoice.getRules().add(rule);
                invoice.getLines().add(line);
                invoice.setCustomer(iCust);

                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                ipw.store(invoice);
            }
        }.execute();

        /* 2. read that invoice and connect the subline to the rule */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                invoice = Factory.create(Invoice.class);
                invoice.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> ipw = openPw(Invoice.class);
                invoice = ipw.read(invoice);
                InvoiceRule rule = invoice.getRules().iterator().next();
                InvoiceSubLine sLine = invoice.getLines().iterator().next().getSubLines().iterator().next();
                sLine.setRule(rule);
                ipw.update(invoice);
            }
        }.execute();

        /* 3. read that invoice again using HQL */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                InvoiceHqlQuery q = new InvoiceHqlQuery();
                q.setInvoiceNumber(invoiceNo);
                q.init(getProvider());
                q.open();
                q.ask();
                invoice = q.getAnswer();
                q.close();

                ruleRef = invoice.getRules().iterator().next();
            }
        }.execute();

        /* 4. re-attach that invoice and test getting the rule and the subrules from the sline */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                try {
                    HibernateSessionProvider hib = getProvider().getResource("LOCALDB", //$NON-NLS-1$
                            HibernateSessionProvider.class);
                    Session session = hib.getHibernateSession();
                    session.update(invoice);
                } catch (InitializationException e) {
                    fail(e.toString());
                }
                InvoiceSubLine sLine = invoice.getLines().iterator().next().getSubLines().iterator().next();
                InvoiceRule rule = sLine.getRule();

                Assert.assertTrue(rule == ruleRef);
                Assert.assertEquals(rule, ruleRef);
                Assert.assertSame(rule, ruleRef);
            }
        }.execute();
    }

    /**
     * This field is a part of the test below.
     * {@link #testManyToOneReferencesAreNotModified()}
     */
    Date customerLmd = null;

    /**
     * Tests that the lastModified fields of Entities referenced
     * 
     * @throws LogicException 
     * @throws DataException 
     * @throws UnexpectedException 
     * 
     */
    @Test
    @SuppressWarnings("nls")
    public void testManyToOneReferencesAreNotModified() throws UnexpectedException, DataException, LogicException {
        /*
         * Store a Customer.
         */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                Customer c = factory.sampleCustomer("AAA");
                PersistenceWorker<Customer> pw = openPw(Customer.class);
                c.setCustomerNo("No1");
                c = pw.store(c);
                customerLmd = c.getLastModified();
            }
        }.execute();

        /*
         * Store an InvoiceCustomer associated with said Customer.
         * Read it and get associated Customer. 
         * Assert that Customer lastModified is intact.
         */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                Customer c = Factory.create(Customer.class);
                PersistenceWorker<Customer> pw = openPw(Customer.class);
                c.setCustomerNo("No1");
                c = pw.read(c);
                Invoice inv = factory.sampleInvoiceFull(1);
                inv.setInvoiceNo(invoiceNo);
                inv.getCustomer().setCustomer(c);
                PersistenceWorker<Invoice> icpw = openPw(Invoice.class);
                inv = icpw.store(inv);
                Assert.assertEquals(customerLmd, inv.getCustomer().getCustomer().getLastModified());
            }
        }.execute();

        /*
         * Read the InvoiceCustomer associated with said Customer.
         * Deep-copy, initialize Customer proxy on copy, store copy, read copy and get associated Customer.
         * Assert that Customer lastModified is intact.
         */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                Invoice inv = Factory.create(Invoice.class);
                inv.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> icpw = openPw(Invoice.class);
                inv = icpw.read(inv);
                Invoice inv2 = PoUtils.deepCopy(inv);
                inv2.setInvoiceNo(copyInvoiceNo);
                inv2.getCustomer().getCustomer().getCustomerName();
                inv2 = icpw.store(inv2);
                Assert.assertEquals(customerLmd, inv2.getCustomer().getCustomer().getLastModified());
            }
        }.execute();

        /*
         * Read the InvoiceCustomer associated with said Customer.
         * Initialize Customer proxy, modify InvoiceCustomer and update it.
         * Assert that Customer lastModified is intact.
         */
        new AbstractBo2RuntimeCmd() {
            @Override
            public void work() throws LogicException, DataException, InitializationException, UnexpectedException {
                Invoice inv = Factory.create(Invoice.class);
                inv.setInvoiceNo(invoiceNo);
                PersistenceWorker<Invoice> icpw = openPw(Invoice.class);
                inv = icpw.read(inv);
                inv.getCustomer().getCustomer().getCustomerName();
                inv.getCustomer().setRoleId(3);
                inv = icpw.update(inv);
                Assert.assertEquals(customerLmd, inv.getCustomer().getCustomer().getLastModified());
            }
        }.execute();

    }

    /*----------------------------------------------------------------------------/
     The following two tests check if a single re-attachment mechanism can satisfy
     all of the following conditions:
     (a) Re-attachment of dirty objects is possible
     (b) Initialization of their proxies is possible after re-attachment
     (c) No SQL is generated for these objects upon flush() even if they are dirty
     (d) Appropriate SQL is generated when explicitly updated with a worker (i.e.
    when session.merge followed by a flush is called.
        
    TODO: delete these tests in the future, when we are sure they are not needed.
    -----------------------------------------------------------------------------*/

    /**
     * THIS TEST IS FOR EXPERIMENTAL USE ONLY. IT IS NOT CERTAIN THAT IT WILL PASS.
     * 
     * Tests re-attaching a detached MODIFIED instance, re-attaching it SOMEHOW,
     * modifying again, checking that lazy proxies can be initialized and finally
     * flushing the session.
     * 
     * The expected outcome is that the changes are not persisted.
     * 
     * <li>transaction1: store
     * <li>transaction2: read
     * <li>transaction3: modify, re-attach, modify and flush
     * <li>transaction4: confirm operations
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     */
    //@Test
    public void testReattach_Modified_Flush() throws UnexpectedException, DataException, LogicException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Now read them */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /* add a new line with a subLine */
        InvoiceLine newLine = factory.sampleInvoiceLine(5);
        newLine.getSubLines().add(factory.sampleInvoiceSubLine(newLine.getLineNo()));
        invoice.getLines().add(newLine);

        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with session.replicate(invoice, ReplicationMode.IGNORE)
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    /* DO MAGIC HERE */
                    session.replicate(invoice, ReplicationMode.IGNORE); //this doesn't work
                    //session.buildLockRequest(LockOptions.NONE).lock(invoice); //this doesn't work
                    /* END DO MAGIC */
                    assertTrue(invoice.getLines().size() == 5);
                    /* add another line */
                    InvoiceLine newLine2 = factory.sampleInvoiceLine(6);
                    newLine2.getSubLines().add(factory.sampleInvoiceSubLine(newLine2.getLineNo()));
                    invoice.getLines().add(newLine2);
                    assertTrue(invoice.getLines().size() == 6);
                    /* finally, add a subLine to each line. */
                    Set<InvoiceLine> lines = invoice.getLines();
                    for (InvoiceLine line : lines) {
                        assertNotNull(line.getSubLines());
                        line.getSubLines().size(); //make sure we can initialize lazy proxies.
                        line.getSubLines().add(factory.sampleInvoiceSubLine(line.getLineNo() + 1));
                    }
                    invoice.tidy();
                    /* 
                     * The invoice now has 6 lines, each one of which has 2 subLines.
                     * We flush (after fixing keys). We will confirm later on that
                     * the changes were not persisted on flush, i.e. that the persisted
                     * invoice has 4 lines, each one of which has 1 subLine.
                     */
                    session.flush();
                } catch (InitializationException e) {
                    fail(e.toString());
                }
            }
        };
        updateOp.setPo(invoice);
        Execute.transactional(updateOp);

        /* confirm results, only 4 lines in DB. Each one has 1 subLine */
        AbstractPersistenceOperation<Invoice> readOp2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                Set<InvoiceLine> lines = invoice.getLines();
                assertTrue(lines.size() == 4);
                for (InvoiceLine line : lines) {
                    assertTrue(line.getSubLines().size() == 1);
                }
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp2.setPo(invoice);
        Execute.transactional(readOp2);
    }

    /**
     * THIS TEST IS FOR EXPERIMENTAL USE ONLY. IT IS NOT CERTAIN THAT IT WILL PASS.
     * 
     * Tests re-attaching a detached MODIFIED instance, re-attaching it SOMEHOW,
     * modifying again, checking that lazy proxies can be initialized and finally
     * updating it with a {@link GenericHibernatePersistenceWorker}.
     * 
     * The expected outcome is that the changes are persisted. The difference
     * with the previous case, is that the worker will call session.merge() and 
     * then flush.
     * 
     * <li>transaction1: store
     * <li>transaction2: read
     * <li>transaction3: modify, re-attach, modify and update with worker
     * <li>transaction4: confirm operations
     * 
     * @throws UnexpectedException
     * @throws DataException
     * @throws LogicException
     */
    //@Test
    public void testReattach_Modified_WorkerUpdate() throws UnexpectedException, DataException, LogicException {

        /* first, store the samples */
        AbstractPersistenceOperation<Invoice> storeOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                po = pw.store(po);
            }
        };
        invoice = factory.sampleInvoiceFull(4);
        invoice.setInvoiceNo(invoiceNo);
        storeOp.setPo(invoice);
        Execute.transactional(storeOp);

        /* Now read them */
        AbstractPersistenceOperation<Invoice> readOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp.setPo(invoice);
        Execute.transactional(readOp);

        /* add a new line with a subLine */
        InvoiceLine newLine = factory.sampleInvoiceLine(5);
        newLine.getSubLines().add(factory.sampleInvoiceSubLine(newLine.getLineNo()));
        invoice.getLines().add(newLine);

        AbstractPersistenceOperation<Invoice> updateOp = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                /* 
                 * re-attach with session.update()
                 * merge() has the same effect, but we call merge in worker.update() anyway.
                 */
                try {
                    Session session = getProvider().getResource("LOCALDB", HibernateSessionProvider.class) //$NON-NLS-1$
                            .getHibernateSession();
                    /* DO MAGIC HERE */
                    session.replicate(invoice, ReplicationMode.IGNORE); //this doesn't work
                    //session.buildLockRequest(LockOptions.NONE).lock(invoice); //this doesn't work
                    /* END DO MAGIC */
                    assertTrue(invoice.getLines().size() == 5);
                    /* add another line */
                    InvoiceLine newLine2 = factory.sampleInvoiceLine(6);
                    newLine2.getSubLines().add(factory.sampleInvoiceSubLine(newLine2.getLineNo()));
                    invoice.getLines().add(newLine2);
                    assertTrue(invoice.getLines().size() == 6);
                    /* finally, add a subLine to each line. */
                    Set<InvoiceLine> lines = invoice.getLines();
                    for (InvoiceLine line : lines) {
                        assertNotNull(line.getSubLines());
                        line.getSubLines().add(factory.sampleInvoiceSubLine(line.getLineNo() + 1));
                    }

                    /* 
                     * The invoice now has 6 lines, each one of which has 2 subLines.
                     * We perform an update with a worker. We will confirm later on that
                     * the changes were persisted on update, i.e. that the persisted
                     * invoice has 6 lines, each one of which has 2 subLines.
                     */
                    pw.update(invoice);
                } catch (InitializationException e) {
                    fail(e.toString());
                }
            }
        };
        updateOp.setPo(invoice);
        Execute.transactional(updateOp);

        /* confirm results, 6 lines. Each one has 2 subLine */
        AbstractPersistenceOperation<Invoice> readOp2 = new AbstractPersistenceOperation<Invoice>(Invoice.class) {
            @Override
            public void execute() throws LogicException, DataException {
                invoice = pw.read(po);
                Set<InvoiceLine> lines = invoice.getLines();
                assertTrue(lines.size() == 6);
                for (InvoiceLine line : lines) {
                    assertTrue(line.getSubLines().size() == 2);
                }
            }
        };
        invoice = Factory.create(Invoice.class);
        invoice.setInvoiceNo(invoiceNo);
        readOp2.setPo(invoice);
        Execute.transactional(readOp2);
    }

    /**
     * HQL query that fetches an Invoice based on an invoiceNo.
     */
    @ManagerName("LOCALDB")
    private class InvoiceHqlQuery extends AbstractHqlQuery<Invoice> {

        /**
         * InvoiceNo
         */
        @Parameter
        String invoiceNumber;

        @Override
        protected String hql() {
            return "select i from Invoice i where i.key.invoiceNo = :invoiceNumber"; //$NON-NLS-1$
        }

        /**
         * Assigns a new value to the invoiceNo.
         *
         * @param invoiceNo the invoiceNo to set
         */
        public void setInvoiceNumber(String invoiceNo) {
            this.invoiceNumber = invoiceNo;
        }

    }

}