/*******************************************************************************
 * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 *     04/07/2012-2.5 Guy Pelletier
 *       - 384275: Customizer from a mapped superclass is not overridden by an entity customizer
 ******************************************************************************/
package org.eclipse.persistence.testing.tests.jpa.inheritance;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.invalidation.TimeToLiveCacheInvalidationPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.indirection.IndirectList;
import org.eclipse.persistence.indirection.IndirectSet;
import org.eclipse.persistence.mappings.CollectionMapping;
import org.eclipse.persistence.sessions.server.ServerSession;
import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.inheritance.AAA;
import org.eclipse.persistence.testing.models.jpa.inheritance.Betta;
import org.eclipse.persistence.testing.models.jpa.inheritance.Bicycle;
import org.eclipse.persistence.testing.models.jpa.inheritance.Bus;
import org.eclipse.persistence.testing.models.jpa.inheritance.CitrusFruit;
import org.eclipse.persistence.testing.models.jpa.inheritance.Company;
import org.eclipse.persistence.testing.models.jpa.inheritance.DDD;
import org.eclipse.persistence.testing.models.jpa.inheritance.Fish;
import org.eclipse.persistence.testing.models.jpa.inheritance.FishTank;
import org.eclipse.persistence.testing.models.jpa.inheritance.InheritanceTableCreator;
import org.eclipse.persistence.testing.models.jpa.inheritance.PerformanceTireInfo;
import org.eclipse.persistence.testing.models.jpa.inheritance.Seed;
import org.eclipse.persistence.testing.models.jpa.inheritance.SeededFruit;
import org.eclipse.persistence.testing.models.jpa.inheritance.SeniorEngineer;
import org.eclipse.persistence.testing.models.jpa.inheritance.SportsCar;
import org.eclipse.persistence.testing.models.jpa.inheritance.Car;
import org.eclipse.persistence.testing.models.jpa.inheritance.Person;
import org.eclipse.persistence.testing.models.jpa.inheritance.PetStore;
import org.eclipse.persistence.testing.models.jpa.inheritance.Engineer;
import org.eclipse.persistence.testing.models.jpa.inheritance.Computer;
import org.eclipse.persistence.testing.models.jpa.inheritance.ComputerPK;
import org.eclipse.persistence.testing.models.jpa.inheritance.Desktop;
import org.eclipse.persistence.testing.models.jpa.inheritance.Laptop;
import org.eclipse.persistence.testing.models.jpa.inheritance.TireInfo;
import org.eclipse.persistence.testing.models.jpa.inheritance.VehicleDirectory;

import junit.framework.Test;
import junit.framework.TestSuite;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

public class EntityManagerJUnitTestCase extends JUnitTestCase {

    public EntityManagerJUnitTestCase() {
        super();
    }

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

    public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.setName("EntityManagerJUnitTestCase");
        suite.addTest(new EntityManagerJUnitTestCase("testSetup"));
        suite.addTest(new EntityManagerJUnitTestCase("testOverriddenCustomizer"));
        suite.addTest(new EntityManagerJUnitTestCase("testAddToUninstantiatedSet"));
        suite.addTest(new EntityManagerJUnitTestCase("testAddDuplicateToUninstantiatedSet"));
        suite.addTest(new EntityManagerJUnitTestCase("testAssociationWithEmbeddedIdSubclassEntityInJoinedStrategy"));
        suite.addTest(new EntityManagerJUnitTestCase("testGenericCollectionOnSuperclass"));
        suite.addTest(new EntityManagerJUnitTestCase("testLazyListInstantiationEager"));
        suite.addTest(new EntityManagerJUnitTestCase("testLazyListInstantiationLazy"));
        suite.addTest(new EntityManagerJUnitTestCase("testLazySetInstantiationEager"));
        suite.addTest(new EntityManagerJUnitTestCase("testMapKeyInheritance"));
        suite.addTest(new EntityManagerJUnitTestCase("testPersistPolymorphicRelationship"));
        suite.addTest(new EntityManagerJUnitTestCase("testRemoveInheritedManyToMany"));
        suite.addTest(new EntityManagerJUnitTestCase("testUpateTireInfo"));
        suite.addTest(new EntityManagerJUnitTestCase("testUpateTireInfo"));
        // EL bug 336486
        suite.addTest(new EntityManagerJUnitTestCase("testCacheExpiryInitializationForInheritance"));
        // Bug 404071
        suite.addTest(new EntityManagerJUnitTestCase("testJoinedInheritanceOneToManyJoinFetch"));
        // Bug 415526
        suite.addTest(new EntityManagerJUnitTestCase("testCascadeMergeWithTargetInheritance"));
        // Bug 458177
        suite.addTest(new EntityManagerJUnitTestCase("testJoinedInheritanceWithAbstractSuperclass"));
        // Bug 355721
        suite.addTest(new EntityManagerJUnitTestCase("testJoinedInheritancePersistWithReadOnlyEntity"));

        return suite;
    }

    /**
     * The setup is done as a test, both to record its failure, and to allow execution in the server.
     */
    public void testSetup() {
        new InheritanceTableCreator().replaceTables(JUnitTestCase.getServerSession());
        clearCache();
    }

    // gf issue 1356 - persisting a polymorphic relationship throws a NPE.
    // The order of persist operations is important for this test.
    public void testPersistPolymorphicRelationship() {
        EntityManager em = createEntityManager();
        beginTransaction(em);
        Person p = new Person();
        p.setName("Evil Knievel");

        Car c = new SportsCar();
        c.setDescription("Ferrari");
        ((SportsCar) c).setMaxSpeed(200);
        p.setCar(c);

        try {
            em.persist(c);
            em.persist(p);
            commitTransaction(em);

        } catch (Exception exception ) {
            fail("Error persisting polymorphic relationship: " + exception.getMessage());
        } finally {
            closeEntityManager(em);
        }
    }

    public void testCacheExpiryInitializationForInheritance() {
        ServerSession session = JUnitTestCase.getServerSession();

        ClassDescriptor personDescriptor = session.getDescriptor(Person.class); // parent
        ClassDescriptor engineerDescriptor = session.getDescriptor(Engineer.class); // subclass
        ClassDescriptor seniorEngineerDescriptor = session.getDescriptor(SeniorEngineer.class); // subclass of subclass

        // Policy existence check
        assertNotNull("personDescriptor's cacheInvalidationPolicy should not be null",
                personDescriptor.getCacheInvalidationPolicy());
        assertNotNull("engineerDescriptor's cacheInvalidationPolicy should not be null",
                engineerDescriptor.getCacheInvalidationPolicy());
        assertNotNull("seniorEngineerDescriptor's cacheInvalidationPolicy should not be null",
                seniorEngineerDescriptor.getCacheInvalidationPolicy());

        // Policy class check
        assertTrue("personDescriptor's cacheInvalidationPolicy should be TimeToLiveCacheInvalidationPolicy",
                personDescriptor.getCacheInvalidationPolicy() instanceof TimeToLiveCacheInvalidationPolicy);
        assertTrue("engineerDescriptor's cacheInvalidationPolicy should be TimeToLiveCacheInvalidationPolicy",
                engineerDescriptor.getCacheInvalidationPolicy() instanceof TimeToLiveCacheInvalidationPolicy);
        assertTrue("seniorEngineerDescriptor's cacheInvalidationPolicy should be TimeToLiveCacheInvalidationPolicy",
                seniorEngineerDescriptor.getCacheInvalidationPolicy() instanceof TimeToLiveCacheInvalidationPolicy);

        // Subclass clone check
        assertFalse("engineerDescriptor's cacheInvalidationPolicy should be a clone",
                engineerDescriptor.getCacheInvalidationPolicy() == personDescriptor.getCacheInvalidationPolicy());
        assertFalse("seniorEngineerDescriptor's cacheInvalidationPolicy should be a clone",
                seniorEngineerDescriptor.getCacheInvalidationPolicy() == personDescriptor.getCacheInvalidationPolicy());

        // Subclass TTL check
        long ttl = ((TimeToLiveCacheInvalidationPolicy)personDescriptor.getCacheInvalidationPolicy()).getTimeToLive();
        assertEquals("engineerDescriptor's invalidation TTL should be " + ttl,
                ttl, ((TimeToLiveCacheInvalidationPolicy)engineerDescriptor.getCacheInvalidationPolicy()).getTimeToLive());
        assertEquals("seniorEngineerDescriptor's invalidation TTL should be " + ttl,
                ttl, ((TimeToLiveCacheInvalidationPolicy)seniorEngineerDescriptor.getCacheInvalidationPolicy()).getTimeToLive());
    }

    /**
     * Verifies that alias name is not customized from an invalid customizer.
     */
    public void testOverriddenCustomizer() {
        ServerSession session = JUnitTestCase.getServerSession();

        ClassDescriptor computerDescriptor = session.getDescriptor(Computer.class);
        assertFalse("Computer alias was incorrect", computerDescriptor.getAlias().equals("InvalidAliasName"));
    }

    // test if we can associate with a subclass entity
    // whose root entity has EmbeddedId in Joined inheritance strategy
    // Issue: GF#1153 && GF#1586 (desktop amendment)
    public void testAssociationWithEmbeddedIdSubclassEntityInJoinedStrategy() {
        EntityManager em = createEntityManager();
        beginTransaction(em);

        try {
            Engineer engineer = new Engineer();
            em.persist(engineer);

            ComputerPK laptopPK = new ComputerPK("Dell", 10001);
            Laptop laptop = em.find(Laptop.class, laptopPK);
            if (laptop == null){
                laptop = new Laptop(laptopPK);
                em.persist(laptop);
            }

            ComputerPK desktopPK = new ComputerPK("IBM", 10002);
            Desktop desktop = em.find(Desktop.class, desktopPK);
            if (desktop == null){
                desktop = new Desktop(desktopPK);
                em.persist(desktop);
            }

            // associate many-to-many relationships
            engineer.getLaptops().add(laptop);
            engineer.getDesktops().add(desktop);

            commitTransaction(em);
        } catch(RuntimeException ex) {
            if (isTransactionActive(em)) {
                rollbackTransaction(em);
            }

            throw ex;
        } finally {
            closeEntityManager(em);
        }
    }

    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=241979
    public void testUpateTireInfo(){
        EntityManager em = createEntityManager();
        beginTransaction(em);
        TireInfo tireInfo = new TireInfo();
        tireInfo.setPressure(35);
        em.persist(tireInfo);
        commitTransaction(em);

        beginTransaction(em);
        TireInfo localTire = em.find(TireInfo.class, tireInfo.getId());
        assertTrue("TireInfo was not persisted with the proper pressure", localTire.getPressure().equals(35));
        localTire.setPressure(40);
        commitTransaction(em);
        em.clear();

        localTire = em.find(TireInfo.class, tireInfo.getId());
        assertTrue("TireInfo was not updated", localTire.getPressure().equals(40));
    }

    //https://bugs.eclipse.org/bugs/show_bug.cgi?id=312253
    // Note: this test will potentially fail in a number of different ways
    // see the above bug for details of the non-derminism
    // also: This test only tests the above bug when weaving is disabled
    // also note: This test is testing for exceptions on the final flush, that
    // is why where are no asserts
    public void testMapKeyInheritance(){
        EntityManager em = createEntityManager();
        beginTransaction(em);

        VehicleDirectory directory = new VehicleDirectory();
        directory.setName("MyVehicles");
        em.persist(directory);

        Company company = new Company();
        company.setName("A Blue Company");
        em.persist(company);
        em.flush();

        Car car = new Car();
        car.setDescription("a Blue Car");
        car.setOwner(company);
        em.persist(car);

        directory.getVehicleDirectory().put(car.getOwner(), car);
        company.getVehicles().add(car);

        try{
            em.flush();
        } catch(RuntimeException e){
            fail("Exception was thrown while flushing a MapKey with inheritance. " + e.getMessage());
        }

        rollbackTransaction(em);;
    }

    public void testRemoveInheritedManyToMany(){
        EntityManager em = createEntityManager();
        beginTransaction(em);
        try{
            SeniorEngineer eng = new SeniorEngineer();
            eng.setName("Vela");
            em.persist(eng);
            Laptop laptop = new Laptop();
            ComputerPK pk = new ComputerPK("Dell", 2111);
            laptop.setComputerPK(pk);
            em.persist(laptop);
            eng.getLaptops().add(laptop);
            em.flush();

            em.remove(eng);
            em.flush();
        } catch (PersistenceException ex){
            if (ex.getCause() instanceof DatabaseException){
                // An Integrity Constraint Violation was the reason for the initial exception
                fail("SQLException thrown when removing an object that inherits a ManyToMany");
            } else {
                fail("UnexpectedException thrown");
            }
        } finally {
            rollbackTransaction(em);
        }
    }

    // Bug 336133
    public void testGenericCollectionOnSuperclass(){
        EntityManager em = createEntityManager();
        beginTransaction(em);
        try{
            Bus bus = new Bus();
            bus.setDescription("a fast bus");
            PerformanceTireInfo tire = new PerformanceTireInfo();
            tire.setPressure(100);
            bus.getTires().add(tire);
            em.persist(bus);
            em.flush();

            em.clear();
            clearCache();

            bus = em.find(Bus.class, bus.getId());
            assertNotNull("Bus is null.", bus);
            assertTrue("Bus has no tires.", bus.getTires().size() == 1);
        } finally {
            rollbackTransaction(em);
        }

    }

    // bug 325035
    public void testAddToUninstantiatedSet(){
        EntityManager em = createEntityManager();
        beginTransaction(em);

        AAA a = new AAA();
        em.persist(a);

        DDD d = new DDD();
        em.persist(d);

        Set<DDD> ds = new HashSet<DDD>();
        ds.add(d);
        a.setDdds(ds);
        d.setAaa(a);
        commitTransaction(em);

        clearCache();
        try{
            em = createEntityManager();
            beginTransaction(em);
            a = em.getReference(AAA.class, a.getId());
            a.getDdds().add(d);
            d.setAaa(a);
            commitTransaction(em);

            assertTrue("The collection contains too many elements", a.getDdds().size() == 1);

        } finally  {
            em = createEntityManager();
            beginTransaction(em);
            a = em.find(AAA.class, a.getId());
            d = em.find(DDD.class, d.getId());
            em.remove(a);
            em.remove(d);
            commitTransaction(em);
        }
    }

     // bug391833 - Duplicate insert on lazy set with attribute change tracking
    public void testAddDuplicateToUninstantiatedSet(){
        String string1 = "String1";
        EntityManager em = createEntityManager();
        beginTransaction(em);
        try{
            AAA a = new AAA();
            Set<String> ds = new HashSet<String>();
            ds.add(string1);
            a.setStringSet(ds);

            em.persist(a);

            em.flush();
            em.clear();

            a = em.find(AAA.class, a.getId());
            em.refresh(a);
            a.getStringSet().add(string1);

            em.flush();

            assertTrue("The collection contains too many elements", a.getStringSet().size() == 1);

            java.util.List results = em.createQuery("select s from AAA a join a.stringSet s where a.id = "+a.getId()).getResultList();
            assertTrue("The Database contains too many elements", results.size() == 1);
        } finally  {
            if (this.isTransactionActive(em)) {
                this.rollbackTransaction(em);
            }
            this.closeEntityManager(em);
        }
    }

    // bug 325035
    public void testLazySetInstantiationLazy(){
        if (!isWeavingEnabled()){
            return;
        }
        EntityManager em = createEntityManager();
        CollectionMapping mapping = ((CollectionMapping)getServerSession().getProject().getClassDescriptor(AAA.class).getMappingForAttributeName("ddds"));
        Boolean lazyIndirection = mapping.shouldUseLazyInstantiationForIndirectCollection();
        mapping.setUseLazyInstantiationForIndirectCollection(true);
        beginTransaction(em);

        AAA a = new AAA();
        em.persist(a);

        DDD d = new DDD();
        em.persist(d);

        Set<DDD> ds = new HashSet<DDD>();
        ds.add(d);
        a.setDdds(ds);
        d.setAaa(a);
        commitTransaction(em);

        clearCache();
        try{
            em = createEntityManager();
            a = em.find(AAA.class, a.getId());
            a.getDdds().add(new DDD());

            assertTrue("Lazy instantiation was not enabled for IndirectSet.", ((IndirectSet)a.getDdds()).getAddedElements().size() == 1);

        } finally  {
            em = createEntityManager();
            beginTransaction(em);
            a = em.find(AAA.class, a.getId());
            d = em.find(DDD.class, d.getId());
            em.remove(a);
            em.remove(d);
            commitTransaction(em);
            mapping.setUseLazyInstantiationForIndirectCollection(lazyIndirection);
        }
    }

    // bug 325035
    public void testLazySetInstantiationEager(){
        EntityManager em = createEntityManager();
        beginTransaction(em);

        AAA a = new AAA();
        em.persist(a);

        DDD d = new DDD();
        em.persist(d);

        Set<DDD> ds = new HashSet<DDD>();
        ds.add(d);
        a.setDdds(ds);
        d.setAaa(a);
        commitTransaction(em);

        clearCache();
        try{
            em = createEntityManager();
            a = em.find(AAA.class, a.getId());
            a.getDdds().add(new DDD());

            assertTrue("Lazy instantiation was not disabled for IndirectSet.", ((IndirectSet)a.getDdds()).getAddedElements().size() == 0);

        } finally  {
            em = createEntityManager();
            beginTransaction(em);
            a = em.find(AAA.class, a.getId());
            d = em.find(DDD.class, d.getId());
            em.remove(a);
            em.remove(d);
            commitTransaction(em);
        }
    }

    // bug 325035
    public void testLazyListInstantiationLazy(){
        if (!isWeavingEnabled()){
            return;
        }
        EntityManager em = createEntityManager();
        beginTransaction(em);

        Company company = new Company();
        company.setName("ListCo");
        em.persist(company);

        Car car = new Car();
        em.persist(car);

        company.getVehicles().add(car);
        car.setOwner(company);

        commitTransaction(em);

        clearCache();
        try{
            em = createEntityManager();
            company = em.find(Company.class, company.getId());
            company.getVehicles().add(new Car());

            assertTrue("Lazy instantiation was not enabled for IndirectList.", ((IndirectList)company.getVehicles()).getAddedElements().size() == 1);

        } finally  {
            em = createEntityManager();
            beginTransaction(em);
            company = em.find(Company.class, company.getId());
            car = em.find(Car.class, car.getId());
            em.remove(company);
            em.remove(car);
            commitTransaction(em);
        }
    }

    // bug 325035
    public void testLazyListInstantiationEager(){
        EntityManager em = createEntityManager();
        CollectionMapping mapping = ((CollectionMapping)getServerSession().getProject().getClassDescriptor(Company.class).getMappingForAttributeName("vehicles"));
        Boolean lazyIndirection = mapping.shouldUseLazyInstantiationForIndirectCollection();
        mapping.setUseLazyInstantiationForIndirectCollection(false);
        beginTransaction(em);

        Company company = new Company();
        company.setName("ListCo");
        em.persist(company);

        Car car = new Car();
        em.persist(car);

        company.getVehicles().add(car);
        car.setOwner(company);

        commitTransaction(em);

        clearCache();
        try{
            em = createEntityManager();
            company = em.find(Company.class, company.getId());
            company.getVehicles().add(new Car());

            assertTrue("Lazy instantiation was not disabled for IndirectList.", ((IndirectList)company.getVehicles()).getAddedElements().size() == 0);

        } finally  {
            em = createEntityManager();
            beginTransaction(em);
            company = em.find(Company.class, company.getId());
            car = em.find(Car.class, car.getId());
            em.remove(company);
            em.remove(car);
            commitTransaction(em);
            mapping.setUseLazyInstantiationForIndirectCollection(lazyIndirection);
        }
    }

    // Bug 404071
    public void testJoinedInheritanceOneToManyJoinFetch() {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            CitrusFruit fruit = new CitrusFruit();
            fruit.setName("Orange");
            fruit.setGrade(1);
            for (int i = 0; i < 4; i++) {
                fruit.addSeed(new Seed());
            }
            em.persist(fruit);

            commitTransaction(em);
            em.clear();
            clearCache();

            // query on superclass
            SeededFruit fruitRead = em.find(SeededFruit.class, fruit.getId());

            assertNotNull("Fruit should not be null", fruitRead);
            assertNotNull("Fruit's seeds should not be null", fruitRead.getSeeds());
            assertTrue("Fruit's seeds should contain 4 elements", fruitRead.getSeeds().size() == 4);
        } finally {
            closeEntityManager(em);
        }
    }

    // Bug 415526
    public void testCascadeMergeWithTargetInheritance() {
        EntityManager em = createEntityManager();
        beginTransaction(em);
        try {
            Company company = new Company();
            company.setName("CascadeMerge, Inc");
            em.persist(company);
            em.flush();

            Bicycle bicycle = new Bicycle();
            bicycle.setDescription("road bike");
            company.getVehicles().add(bicycle);
            bicycle.setOwner(company);
            company = em.merge(company);
            em.flush();

            Number bicycleId = company.getVehicles().iterator().next().getId();
            String classDiscriminatorValue = (String)em.createNativeQuery("SELECT VEH_TYPE FROM CMP3_VEHICLE WHERE ID = " + bicycleId).getSingleResult();

            if (classDiscriminatorValue == null) {
                fail("Class discriminator value written into the db for the merged Bicycle is null");
            }
        } finally {
            rollbackTransaction(em);
            closeEntityManager(em);
        }
    }

    /*
     * Added for Bug 458177
     * - Instantiate a FishTank, unidirectionally lazy-referencing an
     *   abstract Entity (Fish). Persist.
     * - Instantiate a Betta (concrete extension of Fish) and reference
     *   from FishTank's 'fishes' collection. Persist and commit.
     * - Acquire new EM and retrieve cached FishTank. Remove Entity.
     * - This 'simple' operation previously attempted to instantiate the abstract
     *   Fish class during UOW resume (Resulting in InstantiationException).
     */
    public void testJoinedInheritanceWithAbstractSuperclass() {
        Long tankId = null;
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            Betta fish = new Betta();
            fish.setName("swimmy");
            fish.setColor("blue");

            em.persist(fish);

            FishTank fishTank = new FishTank();
            List<Fish> list = new ArrayList<Fish>();
            list.add(fish);
            fishTank.setFishes(list);

            em.persist(fishTank);

            tankId = fishTank.getId();

            commitTransaction(em);
        } finally {
            closeEntityManager(em);
        }

        assertNotNull("FishTank ID should not be null", tankId);

        em = createEntityManager();
        try {
            beginTransaction(em);
            FishTank fishTank = em.find(FishTank.class, tankId);
            assertNotNull("FishTank should not be null", fishTank);

            em.remove(fishTank);

            commitTransaction(em);
        } finally {
            closeEntityManager(em);
        }
    }

    /*
     * Added for Bug 355721
     * - Instantiate and persist (but do not associate) a FishTank Entity and
     *   PetStore Entity.
     * - Clear the EMF cache
     * - Acquire new EM and retrieve the PetStore Entity
     * - Retrieve all FishTank Entities with a RO NamedQuery
     * - Associate the PetStore (lazy unidirectional 1:M) with a FishTank, persist.
     * - This operation previously failed with a ClassCastException.
     */
    public void testJoinedInheritancePersistWithReadOnlyEntity() {
        EntityManager em = createEntityManager();
        Long petStoreId = null;

        try {
            beginTransaction(em);

            FishTank fishTank = new FishTank();
            em.persist(fishTank);

            PetStore petStore = new PetStore();
            petStore.setStoreName("Bob's Fish");
            petStore.setFishTanks(new ArrayList<FishTank>());

            em.persist(petStore);
            petStoreId = petStore.getId();

            commitTransaction(em);
        } finally {
            closeEntityManager(em);
        }

        // cache clear is necessary (reset lazy loading)
        getEntityManagerFactory().getCache().evictAll();

        em = createEntityManager();
        try {
            beginTransaction(em);

            PetStore petStore = em.find(PetStore.class, petStoreId);

            Query query = em.createNamedQuery("findAllFishTanks");
            query.setHint(QueryHints.READ_ONLY, HintValues.TRUE);

            List<FishTank> allFishTanks = query.getResultList();
            FishTank fishTank = allFishTanks.get(0);

            // add read-only entity to referencing unidirectional relationship
            petStore.getFishTanks().add(fishTank);
            em.persist(petStore);

            commitTransaction(em);
        } finally {
            closeEntityManager(em);
        }
    }

}
