/*******************************************************************************
 * Copyright (c) 2011, 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:
 *     03/23/2011-2.3 Guy Pelletier
 *       - 337323: Multi-tenant with shared schema support (part 1)
 *     04/01/2011-2.3 Guy Pelletier
 *       - 337323: Multi-tenant with shared schema support (part 2)
 *     04/21/2011-2.3 Guy Pelletier
 *       - 337323: Multi-tenant with shared schema support (part 5)
 *     05/24/2011-2.3 Guy Pelletier
 *       - 345962: Join fetch query when using tenant discriminator column fails.
 *     06/1/2011-2.3 Guy Pelletier
 *       - 337323: Multi-tenant with shared schema support (part 9)
 *     06/30/2011-2.3.1 Guy Pelletier
 *       - 341940: Add disable/enable allowing native queries
 *     09/09/2011-2.3.1 Guy Pelletier
 *       - 356197: Add new VPD type to MultitenantType
 *     11/10/2011-2.4 Guy Pelletier
 *       - 357474: Address primaryKey option from tenant discriminator column
 *     11/15/2011-2.3.2 Guy Pelletier
 *       - 363820: Issue with clone method from VPDMultitenantPolicy
 *     14/05/2012-2.4 Guy Pelletier
 *       - 376603: Provide for table per tenant support for multitenant applications
 *     22/05/2012-2.4 Guy Pelletier
 *       - 380008: Multitenant persistence units with a dedicated emf should force tenant property specification up front.
 *     01/06/2011-2.3 Guy Pelletier
 *       - 371453: JPA Multi-Tenancy in Bidirectional OneToOne Relation throws ArrayIndexOutOfBoundsException
 *     08/11/2012-2.5 Guy Pelletier
 *       - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy.
 *     20/11/2012-2.5 Guy Pelletier
 *       - 394524: Invalid query key [...] in expression
 ******************************************************************************/
package org.eclipse.persistence.testing.tests.jpa.advanced.multitenant;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NamedQuery;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import junit.framework.*;

import org.eclipse.persistence.queries.SQLCall;
import org.eclipse.persistence.sessions.DatabaseSession;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.server.ServerSession;
import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
import org.eclipse.persistence.testing.framework.junit.JUnitTestCaseHelper;

import org.eclipse.persistence.config.EntityManagerProperties;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.jpa.EJBQueryImpl;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.jpa.JpaHelper;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Address;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.AdvancedMultiTenantTableCreator;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Boss;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Candidate;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Capo;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Card;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Contract;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.MafiaFamily;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Mafioso;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Mason;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Party;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.PhoneNumber;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Reward;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Riding;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Soldier;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.SubCapo;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.SubTask;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Supporter;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.SupporterInfo;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.SupporterInfoSub;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Task;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Trowel;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Underboss;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Card;
import org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Envelope;

public class AdvancedMultiTenantJunitTest extends JUnitTestCase {
    public String MULTI_TENANT_VPD_PU;
    public String MULTI_TENANT_PU;
    public String MULTI_TENANT_PU_123;

    public String MULTI_TENANT_TABLE_PER_TENANT_PU;
    public String MULTI_TENANT_TABLE_PER_TENANT_C_PU;

    public static long candidateAId;
    public static long supporter1Id;
    public static long supporter2Id;
    public static int ridingId;
    public static int partyId;
    public static int masonId;

    public static int family707;
    public static int family007;
    public static int family123;
    public static int capo123Id;
    public static int soldier123Id;
    public static List<Integer> family707Mafiosos = new ArrayList<Integer>();
    public static List<Integer> family707Contracts = new ArrayList<Integer>();
    public static List<Integer> family007Mafiosos = new ArrayList<Integer>();
    public static List<Integer> family007Contracts = new ArrayList<Integer>();
    public static List<Integer> family123Mafiosos = new ArrayList<Integer>();
    public static List<Integer> family123Contracts = new ArrayList<Integer>();

    public AdvancedMultiTenantJunitTest() {
        super();
    }

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

    public void setUp() {}

    public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.setName("AdvancedMultiTenantJunitTest");

        suite.addTest(new AdvancedMultiTenantJunitTest("testSetup"));

        suite.addTest(new AdvancedMultiTenantJunitTest("testCreateMafiaFamily707"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testCreateMafiaFamily007"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testCreateEMF123WithoutAllTenantPropertiesSet"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testCreateMafiaFamily123"));

        suite.addTest(new AdvancedMultiTenantJunitTest("testValidateMafiaFamily707"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testValidateMafiaFamily007"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testValidateMafiaFamily707and007WithSameEM"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testValidateMafiaFamily123"));

        suite.addTest(new AdvancedMultiTenantJunitTest("testComplexMultitenantQueries"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testVPDEMPerTenant"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testMultitenantOneToOneReadObjectRead"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testMultitenantPrimaryKeyWithIdClass"));

        suite.addTest(new AdvancedMultiTenantJunitTest("testTablePerTenantA"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testTablePerTenantAQueries"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testTablePerTenantB"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testTablePerTenantBQueries"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testTablePerTenantC"));
        suite.addTest(new AdvancedMultiTenantJunitTest("testTablePerTenantCQueries"));

        return suite;
    }

    public String getMULTI_TENANT_PU() { return "multi-tenant-shared-emf"; }

    public String getMULTI_TENANT_PU_123() { return "multi-tenant-123"; }

    public String getMULTI_TENANT_VPD_PU() { return "multi-tenant-vpd"; }

    public String getMULTI_TENANT_TABLE_PER_TENANT_PU() { return "multi-tenant-table-per-tenant"; }

    public String getMULTI_TENANT_TABLE_PER_TENANT_C_PU(){ return "multi-tenant-table-per-tenant-C"; }

    public EntityManager createSharedEMFEntityManager(){
        return createEntityManager(getMULTI_TENANT_PU());
    }

    public EntityManager create123EntityManager(){
        return createEntityManager(getMULTI_TENANT_PU_123());
    }

    public EntityManager createTenant123EntityManager(){
        Map<String, String> properties = new HashMap<String, String>();
        EntityManager em = null;
        //properties passed in createEntityManager() won't work on server since server side entity manager is injected, so we have "eclipselink.tenant-id" in server persistence.xml
        if (! isOnServer()) {
            properties.putAll(JUnitTestCaseHelper.getDatabaseProperties(getMULTI_TENANT_PU_123()));
            properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "123");
            em = createEntityManager(getMULTI_TENANT_PU_123(), properties);
        }else{
            em = create123EntityManager();
        }
        return em;
    }

    public EntityManager createVPDEntityManager(){
        return createEntityManager(getMULTI_TENANT_VPD_PU());
    }

    public EntityManager createTableTenantEntityManager(){
        return createEntityManager(getMULTI_TENANT_TABLE_PER_TENANT_PU());
    }

    public EntityManager createTableTenantCEntityManager(){
        return createEntityManager(getMULTI_TENANT_TABLE_PER_TENANT_C_PU());
    }
    /**
     * The setup is done as a test, both to record its failure, and to allow execution in the server.
     */
    public void testSetup() {
        new AdvancedMultiTenantTableCreator().replaceTables(((org.eclipse.persistence.jpa.JpaEntityManager) createSharedEMFEntityManager()).getServerSession());
    }

    public void testTablePerTenantA() {
        EntityManager em = createTableTenantEntityManager();

        try {
            beginTransaction(em);

            // Valid to set the table per tenant qualifier now.
            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "A");

            Candidate candidateA = new Candidate();
            candidateA.setName("CA");

            candidateA.addHonor("Raised most money");
            candidateA.addHonor("Highest win margin");

            candidateA.setSalary(9999999);

            Supporter supporter1 = new Supporter();
            supporter1.setName("Supporter1a");
            SupporterInfo supporter1Info = new SupporterInfo();
            supporter1Info.setDescription("Supporter1aDesc");
            SupporterInfoSub supporter1InfoSub = new SupporterInfoSub();
            supporter1InfoSub.setSubDescription("Supporter1aSubDesc");
            supporter1Info.setSubInfo(supporter1InfoSub);
            supporter1.setInfo(supporter1Info);
            candidateA.addSupporter(supporter1);

            Supporter supporter2 = new Supporter();
            supporter2.setName("Supporter2a");
            SupporterInfo supporter2Info = new SupporterInfo();
            supporter2Info.setDescription("Supporter2aDesc");
            SupporterInfoSub supporter2InfoSub = new SupporterInfoSub();
            supporter2InfoSub.setSubDescription("Supporter2aSubDesc");
            supporter2Info.setSubInfo(supporter2InfoSub);
            supporter2.setInfo(supporter2Info);
            candidateA.addSupporter(supporter2);

            Supporter supporter3 = new Supporter();
            supporter3.setName("Supporter3a");
            SupporterInfo supporter3Info = new SupporterInfo();
            supporter3Info.setDescription("Supporter3aDesc");
            SupporterInfoSub supporter3InfoSub = new SupporterInfoSub();
            supporter3InfoSub.setSubDescription("Supporter3aSubDesc");
            supporter3Info.setSubInfo(supporter3InfoSub);
            supporter3.setInfo(supporter3Info);
            candidateA.addSupporter(supporter3);

            Party party = new Party();
            party.setName("Conservatives");
            party.addCandidate(candidateA);

            Riding riding = new Riding();
            riding.setName("Ottawa");
            candidateA.setRiding(riding);

            // Persist our objects.
            em.persist(party);
            em.persist(candidateA);
            em.persist(supporter1);
            em.persist(supporter2);
            em.persist(supporter3);
            em.persist(riding);

            Mason mason = new Mason();
            mason.setName("FromTenantA");
            mason.addAward(Helper.timestampFromDate(Helper.dateFromYearMonthDate(2009, 1, 1)), "Best pointer");
            mason.addAward(Helper.timestampFromDate(Helper.dateFromYearMonthDate(2010, 5, 9)), "Least screw-ups");

            Trowel trowel = new Trowel();
            trowel.setType("Pointing");
            mason.setTrowel(trowel);
            trowel.setMason(mason);

            em.persist(mason);
            em.persist(trowel);

            // Grab any id's for verification.
            candidateAId = candidateA.getId();
            ridingId = riding.getId();
            partyId = party.getId();
            supporter1Id = supporter1.getId();
            supporter2Id = supporter2.getId();
            masonId = mason.getId();

            commitTransaction(em);
        } catch (RuntimeException e) {
            throw e;
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

            closeEntityManager(em);
        }
    }

    public void testTablePerTenantAQueries() {
        EntityManager em = createTableTenantEntityManager();

        try {
            beginTransaction(em);

            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "A");

            List resultsFromNamedQuery = em.createNamedQuery("Supporter.findAll").getResultList();
            List resultsFromDynamicQuery = em.createQuery("SELECT s FROM Supporter s ORDER BY s.id DESC").getResultList();

            assertTrue("Incorrect number of supporters returned from named query.", resultsFromNamedQuery.size() == 3);
            assertTrue("Incorrect number of supporters returned from dynamic query.", resultsFromDynamicQuery.size() == 3);

            // Test some more complex joining queries.
            List<Supporter> results = em.createNamedQuery("Supporter.findBySupporterInfo").setParameter("desc", "Supporter1aDesc").getResultList();
            assertFalse("No results returned.", results == null);
            assertTrue("Single result not returned.", results.size() == 1);
            assertTrue("Didn't return supporter1a", results.get(0).getName().equals("Supporter1a"));
            results = em.createNamedQuery("Supporter.findBySupporterInfoSub").setParameter("subDesc", "Supporter3aSubDesc").getResultList();
            assertFalse("No results returned.", results == null);
            assertTrue("Single result not returned.", results.size() == 1);
            assertTrue("Didn't return supporter3a", results.get(0).getName().equals("Supporter3a"));

            commitTransaction(em);
        } catch (RuntimeException e) {
            throw e;
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

            closeEntityManager(em);
        }
    }

    public void testTablePerTenantB() {
        EntityManager em = createTableTenantEntityManager();

        try {
            beginTransaction(em);

            // Valid to set the table per tenant qualifier now.
            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "B");

            // Should not find these ...
            assertNull("CandidateA was found from tenant B.", em.find(Candidate.class, candidateAId));
            assertNull("Supporter1 was found from tenant B.", em.find(Supporter.class, supporter1Id));
            assertNull("Supporter2 was found from tenant B.", em.find(Supporter.class, supporter2Id));
            assertNull("Mason was found from tenant B.", em.find(Mason.class, masonId));

            // Should find these ...
            Riding riding = em.find(Riding.class, ridingId);
            assertNotNull("Riding was not found from tenant B", riding);
            Party party = em.find(Party.class, partyId);
            assertNotNull("Party was not found from tenant B", party);
            // TODO: can't do this (would have to initialize the mapping from party again to the new descriptor.
            // future implementation maybe.
            //assertTrue("Party had candidates", party.getCandidates().isEmpty());

            Candidate candidateB = new Candidate();
            candidateB.setName("CB");
            candidateB.setSalary(100);
            em.persist(candidateB);
            commitTransaction(em);

            em.clear();
            clearCache(getMULTI_TENANT_TABLE_PER_TENANT_PU());

            beginTransaction(em);

            // Valid to set the table per tenant qualifier now.
            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "B");

            Candidate candidateBRefreshed = em.find(Candidate.class, candidateB.getId());

            assertNull("Candidate B has a Party when he shouldn't have.", candidateBRefreshed.getParty());
            assertNull("Candidate B has a Riding when he shouldn't have.", candidateBRefreshed.getRiding());
            assertTrue("Candidate B had supporters when he shouldn't have.", candidateBRefreshed.getSupporters().isEmpty());
            assertTrue("Candidate B had honors when he shouldn't have.", candidateBRefreshed.getHonors().isEmpty());
            assertTrue("Candidate B had the incorrect salary.", candidateBRefreshed.getSalary() == 100);

            commitTransaction(em);

        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

            throw e;
        } finally {

            closeEntityManager(em);
        }
    }

    public void testTablePerTenantBQueries() {
        EntityManager em = createTableTenantEntityManager();

        try {
            beginTransaction(em);

            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "B");

            List resultsFromNamedQuery = em.createNamedQuery("Supporter.findAll").getResultList();
            List resultsFromDynamicQuery = em.createQuery("SELECT s FROM Supporter s ORDER BY s.id DESC").getResultList();

            assertTrue("Incorrect number of supporters returned from named query.", resultsFromNamedQuery.isEmpty());
            assertTrue("Incorrect number of supporters returned from dynamic query.", resultsFromDynamicQuery.isEmpty());

            commitTransaction(em);
        } catch (RuntimeException e) {
            throw e;
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

            closeEntityManager(em);
        }
    }

    public void testTablePerTenantC() {
        // Tenant is set in the persistence.xml file.
        EntityManager em = createTableTenantCEntityManager();

        try {
            beginTransaction(em);

            Candidate candidate = new Candidate();
            candidate.setName("C");

            candidate.addHonor("Raised most money");
            candidate.addHonor("Highest win margin");

            candidate.setSalary(9999999);

            Supporter supporter1 = new Supporter();
            supporter1.setName("Supporter1c");
            candidate.addSupporter(supporter1);

            Supporter supporter2 = new Supporter();
            supporter2.setName("Supporter2c");
            candidate.addSupporter(supporter2);

            Party party = new Party();
            party.setName("Conservatives");
            party.addCandidate(candidate);

            Riding riding = new Riding();
            riding.setName("Ottawa");
            candidate.setRiding(riding);

            // Persist our objects.
            em.persist(party);
            em.persist(candidate);
            em.persist(supporter2);
            em.persist(supporter1);
            em.persist(riding);

            Mason mason = new Mason();
            mason.setName("FromTenantC");
            mason.addAward(Helper.timestampFromDate(Helper.dateFromYearMonthDate(2009, 1, 1)), "Best pointer");
            mason.addAward(Helper.timestampFromDate(Helper.dateFromYearMonthDate(2010, 5, 9)), "Least screw-ups");

            Trowel trowel = new Trowel();
            trowel.setType("Pointing");
            mason.setTrowel(trowel);
            trowel.setMason(mason);

            em.persist(mason);
            em.persist(trowel);

            // Grab any id's for verification.
            candidateAId = candidate.getId();
            ridingId = riding.getId();
            partyId = party.getId();
            supporter1Id = supporter1.getId();
            supporter2Id = supporter2.getId();
            masonId = mason.getId();

            commitTransaction(em);
        } catch (RuntimeException e) {
            throw e;
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

            closeEntityManager(em);
        }
    }

    public void testTablePerTenantCQueries() {
        EntityManager em = createTableTenantCEntityManager();

        try {
            beginTransaction(em);

            List resultsFromNamedQuery = em.createNamedQuery("Supporter.findAll").getResultList();
            List resultsFromDynamicQuery = em.createQuery("SELECT s FROM Supporter s ORDER BY s.id DESC").getResultList();

            assertTrue("Incorrect number of supporters returned from named query.", resultsFromNamedQuery.size() == 2);
            assertTrue("Incorrect number of supporters returned from dynamic query.", resultsFromDynamicQuery.size() == 2);

            commitTransaction(em);
        } catch (RuntimeException e) {
            throw e;
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

            closeEntityManager(em);
        }
    }

    public void testCreateMafiaFamily707() {
        EntityManager em = createSharedEMFEntityManager();

        try {
            beginTransaction(em);
            //on server side, you have to set the em properties after transaction begins
            em.setProperty("tenant.id", "707");
            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");

            MafiaFamily family = new MafiaFamily();
            family.setName("Gonzo");
            family.setRevenue(10000000.00);
            family.addTag("firstTag");
            family.addTag("secondTag");
            family.addTag("thirdTag");

            Boss boss = new Boss();
            boss.setFirstName("707");
            boss.setLastName("Boss");
            boss.setGender(Mafioso.Gender.Male);

            Underboss underboss = new Underboss();
            underboss.setFirstName("Under");
            underboss.setLastName("Boss");
            underboss.setGender(Mafioso.Gender.Male);

            Capo capo1 = new Capo();
            capo1.setFirstName("Capo");
            capo1.setLastName("Giggaloo");
            capo1.setGender(Mafioso.Gender.Female);

            Capo capo2 = new Capo();
            capo2.setFirstName("Capo");
            capo2.setLastName("CrazyGlue");
            capo2.setGender(Mafioso.Gender.Male);

            Soldier soldier1 = new Soldier();
            soldier1.setFirstName("Soldier");
            soldier1.setLastName("One");
            soldier1.setGender(Mafioso.Gender.Female);

            Soldier soldier2 = new Soldier();
            soldier2.setFirstName("Soldier");
            soldier2.setLastName("Two");
            soldier2.setGender(Mafioso.Gender.Male);

            Soldier soldier3 = new Soldier();
            soldier3.setFirstName("Soldier");
            soldier3.setLastName("Three");
            soldier3.setGender(Mafioso.Gender.Male);

            Soldier soldier4 = new Soldier();
            soldier4.setFirstName("Soldier");
            soldier4.setLastName("Four");
            soldier4.setGender(Mafioso.Gender.Male);

            Soldier soldier5 = new Soldier();
            soldier5.setFirstName("Soldier");
            soldier5.setLastName("Four");
            soldier5.setGender(Mafioso.Gender.Female);

            Contract contract1 = new Contract();
            contract1.setDescription("Whack 007 family boss");

            Contract contract2 = new Contract();
            contract2.setDescription("Acquire fully-automatic guns");

            Contract contract3 = new Contract();
            contract3.setDescription("Steal some money");

            // Populate the relationships.
            contract1.addSoldier(soldier1);
            contract1.addSoldier(soldier5);

            contract2.addSoldier(soldier1);
            contract2.addSoldier(soldier3);
            contract2.addSoldier(soldier5);

            contract3.addSoldier(soldier2);
            contract3.addSoldier(soldier3);
            contract3.addSoldier(soldier4);
            contract3.addSoldier(soldier5);

            boss.setUnderboss(underboss);

            capo1.setUnderboss(underboss);
            capo2.setUnderboss(underboss);

            capo1.addSoldier(soldier1);
            capo1.addSoldier(soldier2);

            capo2.addSoldier(soldier3);
            capo2.addSoldier(soldier4);
            capo2.addSoldier(soldier5);

            underboss.addCapo(capo1);
            underboss.addCapo(capo2);

            family.addMafioso(boss);
            family.addMafioso(underboss);

            family.addMafioso(capo1);
            family.addMafioso(capo2);

            family.addMafioso(soldier1);
            family.addMafioso(soldier2);
            family.addMafioso(soldier3);
            family.addMafioso(soldier4);
            family.addMafioso(soldier5);

            // Will cascade through the whole family.
            em.persist(family);

            // Store the ids to verify
            family707 = family.getId();
            family707Mafiosos.add(boss.getId());
            family707Mafiosos.add(underboss.getId());
            family707Mafiosos.add(capo1.getId());
            family707Mafiosos.add(capo2.getId());
            family707Mafiosos.add(soldier1.getId());
            family707Mafiosos.add(soldier2.getId());
            family707Mafiosos.add(soldier3.getId());
            family707Mafiosos.add(soldier4.getId());
            family707Mafiosos.add(soldier5.getId());
            family707Contracts.add(contract1.getId());
            family707Contracts.add(contract2.getId());
            family707Contracts.add(contract3.getId());
            commitTransaction(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

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

    public void testCreateMafiaFamily007() {
        EntityManager em = createSharedEMFEntityManager();
        try {
            beginTransaction(em);
            //on server side, you have to set the em properties after transaction begins
            em.setProperty("tenant.id", "007");
            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "007");

            MafiaFamily family = new MafiaFamily();
            family.setName("Bond");
            family.setRevenue(987654321.03);
            family.addTag("tag1");
            family.addTag("tag2");
            family.addTag("tag3");
            family.addTag("tag4");
            family.addTag("tag5");

            Boss boss = new Boss();
            boss.setFirstName("007");
            boss.setLastName("Boss");
            boss.setGender(Mafioso.Gender.Female);

            Underboss underboss = new Underboss();
            underboss.setFirstName("Second");
            underboss.setLastName("InCommand");
            underboss.setGender(Mafioso.Gender.Female);

            Capo capo1 = new Capo();
            capo1.setFirstName("Capo");
            capo1.setLastName("Lubey");
            capo1.setGender(Mafioso.Gender.Male);

            Capo capo2 = new Capo();
            capo2.setFirstName("Capo");
            capo2.setLastName("Greasy");
            capo2.setGender(Mafioso.Gender.Female);

            Soldier soldier1 = new Soldier();
            soldier1.setFirstName("First");
            soldier1.setLastName("Grunt");
            soldier1.setGender(Mafioso.Gender.Male);

            Soldier soldier2 = new Soldier();
            soldier2.setFirstName("Second");
            soldier2.setLastName("Grunt");
            soldier2.setGender(Mafioso.Gender.Female);

            Soldier soldier3 = new Soldier();
            soldier3.setFirstName("Third");
            soldier3.setLastName("Grunt");
            soldier3.setGender(Mafioso.Gender.Female);

            Soldier soldier4 = new Soldier();
            soldier4.setFirstName("Fourth");
            soldier4.setLastName("Grunt");
            soldier4.setGender(Mafioso.Gender.Female);

            Soldier soldier5 = new Soldier();
            soldier5.setFirstName("Fifth");
            soldier5.setLastName("Grunt");
            soldier5.setGender(Mafioso.Gender.Male);

            Soldier soldier6 = new Soldier();
            soldier6.setFirstName("Sixth");
            soldier6.setLastName("Grunt");
            soldier6.setGender(Mafioso.Gender.Male);

            Soldier soldier7 = new Soldier();
            soldier7.setFirstName("Seventh");
            soldier7.setLastName("Grunt");
            soldier7.setGender(Mafioso.Gender.Male);

            Contract contract1 = new Contract();
            contract1.setDescription("Whack 707 family boss");

            Contract contract2 = new Contract();
            contract2.setDescription("Acquire semi-automatic guns");

            Contract contract3 = new Contract();
            contract3.setDescription("Set up new financing deals");

            // Populate the relationships.
            contract1.addSoldier(soldier1);
            contract1.addSoldier(soldier5);

            contract2.addSoldier(soldier1);
            contract2.addSoldier(soldier3);
            contract2.addSoldier(soldier7);

            contract3.addSoldier(soldier2);
            contract3.addSoldier(soldier3);
            contract3.addSoldier(soldier4);
            contract3.addSoldier(soldier5);

            boss.setUnderboss(underboss);

            capo1.setUnderboss(underboss);
            capo2.setUnderboss(underboss);

            capo1.addSoldier(soldier1);
            capo1.addSoldier(soldier2);

            capo2.addSoldier(soldier3);
            capo2.addSoldier(soldier4);
            capo2.addSoldier(soldier5);
            capo2.addSoldier(soldier6);
            capo2.addSoldier(soldier7);

            underboss.addCapo(capo1);
            underboss.addCapo(capo2);

            family.addMafioso(boss);
            family.addMafioso(underboss);

            family.addMafioso(capo1);
            family.addMafioso(capo2);

            family.addMafioso(soldier1);
            family.addMafioso(soldier2);
            family.addMafioso(soldier3);
            family.addMafioso(soldier4);
            family.addMafioso(soldier5);
            family.addMafioso(soldier6);
            family.addMafioso(soldier7);

            // Will cascade through the whole family.
            em.persist(family);
            family007 = family.getId();
            family007Mafiosos.add(boss.getId());
            family007Mafiosos.add(underboss.getId());
            family007Mafiosos.add(capo1.getId());
            family007Mafiosos.add(capo2.getId());
            family007Mafiosos.add(soldier1.getId());
            family007Mafiosos.add(soldier2.getId());
            family007Mafiosos.add(soldier3.getId());
            family007Mafiosos.add(soldier4.getId());
            family007Mafiosos.add(soldier5.getId());
            family007Mafiosos.add(soldier6.getId());
            family007Mafiosos.add(soldier7.getId());
            family007Contracts.add(contract1.getId());
            family007Contracts.add(contract2.getId());
            family007Contracts.add(contract3.getId());
            commitTransaction(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

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

    /**
     * This test is expected to fail.
     */
    public void testCreateEMF123WithoutAllTenantPropertiesSet() {
        EntityManager em = null;
        boolean exceptionCaught = false;
        try {
            em = create123EntityManager();
        } catch (RuntimeException e) {
            exceptionCaught = true;
        } finally {
            if (em != null) {
                closeEntityManager(em);
            }

            closeEntityManagerFactory(getMULTI_TENANT_PU_123());
        }

        assertTrue("No exception received on a non shared emf without all tenant properties provided", exceptionCaught);
    }

    public void testCreateMafiaFamily123() {
        EntityManager em = createTenant123EntityManager();
        try {
            beginTransaction(em);

            MafiaFamily family = new MafiaFamily();
            family.setName("Galore");
            family.setRevenue(4321.03);
            family.addTag("newtag1");

            Boss boss = new Boss();
            boss.setFirstName("Kitty");
            boss.setLastName("Galore");
            boss.setGender(Mafioso.Gender.Female);
            boss.addReward("Reward 1");
            boss.addReward("Reward 2");
            boss.setAddress(new Address("1st Street", "Ottawa", "Ontario", "Canada", "B1T 2Y1"));

            Underboss underboss = new Underboss();
            underboss.setFirstName("Number2");
            underboss.setLastName("Galore");
            underboss.setGender(Mafioso.Gender.Male);
            underboss.addReward("Reward 3");
            underboss.addReward("Reward 4");
            underboss.setAddress(new Address("2nd Street", "Ottawa", "Ontario", "Canada", "B1T 2Y2"));

            Capo capo1 = new Capo();
            capo1.setFirstName("Capo");
            capo1.setLastName("Galore");
            capo1.setGender(Mafioso.Gender.Male);
            capo1.setAddress(new Address("3rd Street", "Ottawa", "Ontario", "Canada", "B1T 2Y3"));

            Capo capo2 = new Capo();
            capo2.setFirstName("Drill");
            capo2.setLastName("Bit");
            capo2.setGender(Mafioso.Gender.Male);
            capo2.setAddress(new Address("4th Street", "Ottawa", "Ontario", "Canada", "B1T 2Y4"));

            Soldier soldier1 = new Soldier();
            soldier1.setFirstName("Grunt");
            soldier1.setLastName("Galore");
            soldier1.setGender(Mafioso.Gender.Male);
            soldier1.addReward("Reward 5");
            soldier1.addReward("Reward 6");
            soldier1.addReward("Reward 7");
            soldier1.addReward("Reward 8");
            soldier1.setAddress(new Address("5th Street", "Ottawa", "Ontario", "Canada", "B1T 2Y5"));

            Soldier soldier2 = new Soldier();
            soldier2.setFirstName("Hammer");
            soldier2.setLastName("Head");
            soldier2.setGender(Mafioso.Gender.Male);
            soldier2.addReward("Reward 9");
            soldier2.addReward("Reward 10");
            soldier2.setAddress(new Address("6th Street", "Ottawa", "Ontario", "Canada", "B1T 2Y6"));

            Contract contract1 = new Contract();
            contract1.setDescription("Whack all other family bosses");

            Contract contract2 = new Contract();
            contract2.setDescription("Pillage, pillage, pillage!");

            // Populate the relationships.
            contract1.addSoldier(soldier1);
            contract2.addSoldier(soldier1);

            boss.setUnderboss(underboss);

            capo1.setUnderboss(underboss);
            capo1.addSoldier(soldier1);

            capo2.addSoldier(soldier2);

            underboss.addCapo(capo1);
            underboss.addCapo(capo2);

            family.addMafioso(boss);
            family.addMafioso(underboss);

            family.addMafioso(capo1);
            family.addMafioso(capo2);

            family.addMafioso(soldier1);
            family.addMafioso(soldier2);

            // Will cascade through the whole family.
            em.persist(family);
            capo123Id = capo1.getId();
            soldier123Id = soldier1.getId();
            family123 = family.getId();
            family123Mafiosos.add(boss.getId());
            family123Mafiosos.add(underboss.getId());
            family123Mafiosos.add(capo1.getId());
            family123Mafiosos.add(soldier1.getId());
            family123Contracts.add(contract1.getId());
            family123Contracts.add(contract2.getId());
            commitTransaction(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

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

    public void testValidateMafiaFamily707() {
        EntityManager em = createSharedEMFEntityManager();

        try {
            validateMafiaFamily707(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            throw e;
        } finally {
            closeEntityManager(em);
        }
    }

    public void testValidateMafiaFamily007() {
        EntityManager em = createSharedEMFEntityManager();

        try {
            validateMafiaFamily007(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            throw e;
        } finally {
            closeEntityManager(em);
        }
    }

    public void testValidateMafiaFamily707and007WithSameEM() {
        EntityManager em = createSharedEMFEntityManager();

        try {
            validateMafiaFamily707(em);
            validateMafiaFamily007(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            throw e;
        } finally {
            closeEntityManager(em);
        }
    }

    public void testValidateMafiaFamily123() {
        EntityManager em = createTenant123EntityManager();

        try {
            clearCache(getMULTI_TENANT_PU_123());
            em.clear();

            MafiaFamily family =  em.find(MafiaFamily.class, family123);
            assertNotNull("The Mafia Family with id: " + family123 + ", was not found", family);
            assertTrue("The Mafia Family had an incorrect number of tags [" + family.getTags().size() + "], expected [1]", family.getTags().size() == 1);
            assertNull("The Mafia Family with id: " + family707 + ", was found (when it should not have been)", em.find(MafiaFamily.class, family707));
            assertNull("The Mafia Family with id: " + family007 + ", was found (when it should not have been)", em.find(MafiaFamily.class, family007));
            assertFalse("No mafiosos part of 123 family", family.getMafiosos().isEmpty());

            // See if we can find any members of the other family.
            for (Integer id : family707Mafiosos) {
                assertNull("Found family 707 mafioso.", em.find(Mafioso.class, id));
            }

            // Try a native sql query. Should get an exception since the
            // eclipselink.jdbc.allow-native-sql-queries property is set to
            // false for this PU.
            boolean exceptionCaught = false;
            List mafiaFamilies = null;
            try {
                mafiaFamilies = em.createNativeQuery("select * from JPA_MAFIA_FAMILY").getResultList();
            } catch (Exception e) {
                exceptionCaught = true;
            }

            assertTrue("No exception was caught from issuing a native query.", exceptionCaught);

            exceptionCaught = false;
            try {
                mafiaFamilies = em.createNamedQuery("findSQLMafiaFamilies").getResultList();
            } catch (Exception e) {
                exceptionCaught = true;
            }

            assertTrue("No exception was caught from issuing a named native query.", exceptionCaught);


            // Query directly for the boss from the other family.
            Boss otherBoss = em.find(Boss.class, family707Mafiosos.get(0));
            assertNull("Found family 707 boss.", otherBoss);

            // See if we can find any contracts of the other family.
            for (Integer id : family707Contracts) {
                assertNull("Found family 707 contract. ", em.find(Contract.class, id));
            }

            // Try a select named query
            List families = em.createNamedQuery("findAllMafiaFamilies").getResultList();
            assertTrue("Incorrect number of families were returned [" + families.size() + "], expected [1]",  families.size() == 1);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            throw e;
        } finally {
            closeEntityManager(em);
        }
    }

    protected void validateMafiaFamily007(EntityManager em) {
        clearCache(getMULTI_TENANT_PU());
        em.clear();
        beginTransaction(em);

        em.setProperty("tenant.id", "007");
        em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "007");

        MafiaFamily family =  em.find(MafiaFamily.class, family007);
        assertNotNull("The Mafia Family with id: " + family007 + ", was not found", family);
        assertTrue("The Mafia Family had an incorrect number of tags [" + family.getTags().size() + "], expected [5]", family.getTags().size() == 5);
        assertNull("The Mafia Family with id: " + family707 + ", was found (when it should not have been)", em.find(MafiaFamily.class, family707));
        assertNull("The Mafia Family with id: " + family123 + ", was found (when it should not have been)", em.find(MafiaFamily.class, family123));
        assertFalse("No mafiosos part of 007 family", family.getMafiosos().isEmpty());

        // See if we can find any members of the other family.
        for (Integer id : family707Mafiosos) {
            assertNull("Found family 707 mafioso.", em.find(Mafioso.class, id));
        }

        // Query directly for the boss from the other family.
        Boss otherBoss = em.find(Boss.class, family707Mafiosos.get(0));
        assertNull("Found family 707 boss.", otherBoss);

        // See if we can find any contracts of the other family.
        for (Integer id : family707Contracts) {
            assertNull("Found family 707 contract.", em.find(Contract.class, id));
        }

        // Read and validate our contracts
        List<Contract> contracts = em.createNamedQuery("FindAllContracts").getResultList();
        assertTrue("Incorrect number of contracts were returned [" + contracts.size() + "], expected[3]", contracts.size() == 3);

        for (Contract contract : contracts) {
            assertFalse("Contract description was voided.", contract.getDescription().equals("voided"));
        }

        // Try a select named query
        List families = em.createNamedQuery("findAllMafiaFamilies").getResultList();
        assertTrue("Incorrect number of families were returned [" + families.size() + "], expected [1]",  families.size() == 1);

        // Find our boss and make sure his name has not been compromised from the 707 family.
        Boss boss = em.find(Boss.class, family007Mafiosos.get(0));
        assertFalse("The Boss name has been compromised", boss.getFirstName().equals("Compromised"));
        commitTransaction(em);
    }

    protected void validateMafiaFamily707(EntityManager em) {
        clearCache(getMULTI_TENANT_PU());
        em.clear();

        beginTransaction(em);

        em.setProperty("tenant.id", "707");
        em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
        MafiaFamily family = em.find(MafiaFamily.class, family707);
        assertNotNull("The Mafia Family with id: " + family707 + ", was not found", family);
        assertTrue("The Mafia Family had an incorrect number of tags [" + family.getTags().size() + "], expected [3]", family.getTags().size() == 3);
        assertNull("The Mafia Family with id: " + family007 + ", was found (when it should not have been)", em.find(MafiaFamily.class, family007));
        assertNull("The Mafia Family with id: " + family123 + ", was found (when it should not have been)", em.find(MafiaFamily.class, family123));
        assertFalse("No mafiosos part of 707 family", family.getMafiosos().isEmpty());

        // See if we can find any members of the other family.
        for (Integer id : family007Mafiosos) {
            assertNull("Found family 007 mafioso.", em.find(Mafioso.class, id));
        }

        // Query directly for the boss from the other family.
        Boss otherBoss = em.find(Boss.class, family007Mafiosos.get(0));
        assertNull("Found family 007 boss.", otherBoss);

        // See if we can find any contracts of the other family.
        for (Integer id : family007Contracts) {
            assertNull("Found family 007 contract.", em.find(Contract.class, id));
        }

        // Try deleting a contract from the 007 family.
        Query deleteQuery = em.createNamedQuery("DeleteContractByPrimaryKey");
        deleteQuery.setParameter("id", family007Contracts.get(0));
        int result = deleteQuery.executeUpdate();
        assertTrue("Was able to delete a contract from the 007 family", result == 0);

        // Update all our contract descriptions to be 'voided'
        Query updateAllQuery = em.createNamedQuery("UpdateAllContractDescriptions");
        updateAllQuery.executeUpdate();
        // Need to check that tenant id column is present
        assertTrue("Tenant discriminator column not found in update all query", ((EJBQueryImpl) updateAllQuery).getDatabaseQuery().getCall().getSQLString().contains("TENANT_ID"));

        // Read and validate the contracts
        List<Contract> contracts = em.createNamedQuery("FindAllContracts").getResultList();
        int contractNumber = contracts.size();
        assertTrue("Incorrect number of contracts were returned [" + contracts.size() + "], expected [3]", contracts.size() == 3);

        for (Contract contract : contracts) {
            assertTrue("Contract description was not voided.", contract.getDescription().equals("voided"));
        }

        // See how many soldiers are returned from a jpql query
        List soldiers = em.createQuery("SELECT s from Soldier s").getResultList();
        assertTrue("Incorrect number of soldiers were returned [" + soldiers.size() + "], expected [5]",  soldiers.size() == 5);

        if(getServerSession(getMULTI_TENANT_PU()).getPlatform().isSymfoware()) {
            getServerSession(getMULTI_TENANT_PU()).logMessage("Test AdvancedMultiTenantJunitTest partially skipped for this platform, "
                    +"which uses UpdateAll internally to check tenant-id when updating an entity using JOINED inheritance strategy. "
                    +"Symfoware doesn't support UpdateAll/DeleteAll on multi-table objects (see rfe 298193).");
            commitTransaction(em);
            return;
        }

        // We know what the boss's id is for the 007 family to try to update him from the 707 pu.
        // The 007 family is validated after this test.
        Query query = em.createNamedQuery("UpdateBossName");
        query.setParameter("name", "Compromised");
        query.setParameter("id", family007Mafiosos.get(0));
        query.executeUpdate();
        commitTransaction(em);
    }

    public void testComplexMultitenantQueries() {
        EntityManager em = createTenant123EntityManager();

        try {
            clearCache(getMULTI_TENANT_PU_123());
            em.clear();

            // Try passing in a sub entity as a parameter.
            try {
                Query q = em.createQuery("SELECT s FROM Soldier s WHERE s.capo=?1");
                SubCapo subCapo = new SubCapo();
                subCapo.setId(capo123Id);
                q.setParameter(1, subCapo);
                List<Soldier> soldiers = q.getResultList();
                assertTrue("Incorrect number of soldiers returned [" + soldiers.size() +"], expected [1]", soldiers.size() == 1);
                assertTrue("Mafioso returned was not a soldier", soldiers.get(0).isSoldier());
                assertTrue("Soldier returned was not the expected soldier", soldiers.get(0).getId() == soldier123Id);
            } catch (Exception e) {
                fail("Exception encountered on named parameter query (with tenant discriminator columns) : " + e);
            }

            // Try a join fetch
            try {
                TypedQuery<MafiaFamily> q = em.createQuery("SELECT m FROM MafiaFamily m ORDER BY m.id DESC", MafiaFamily.class);
                q.setHint(QueryHints.FETCH, "m.mafiosos");
                q.getResultList();
            } catch (Exception e) {
                fail("Exception encountered on join fetch query (with tenant discriminator columns): " + e);
            }

            // Try a nested join fetch
            try {
                TypedQuery<MafiaFamily> q = em.createQuery("SELECT f FROM MafiaFamily f ORDER BY f.id ASC", MafiaFamily.class);
                q.setHint(QueryHints.FETCH, "f.mafiosos.rewards");
                q.getResultList();
            } catch (Exception e) {
                fail("Exception encountered on nested join fetch query (with tenant discriminator columns): " + e);
            }

            // Try a batch fetch
            try {
                TypedQuery<MafiaFamily> query = em.createQuery("SELECT f FROM MafiaFamily f", MafiaFamily.class);
                query.setHint(QueryHints.BATCH, "f.mafiosos");
                List<MafiaFamily> families = query.getResultList();

                // Should only be one family
                assertTrue("Incorrect number of families returned [" + families.size() +"], expected [1]", families.size() == 1);

                int size = families.get(0).getMafiosos().size();
                assertTrue("Incorrect number of mafiosos returned [" + size + "], expected [6]", size == 6);

            } catch (Exception e) {
                fail("Exception encountered on batch fetch query (with tenant discriminator columns): " + e);
            }

            // Try a multiple select
            try {
                Query query = em.createQuery("SELECT m.address, m.family FROM Mafioso m WHERE m.address.city = 'Ottawa' AND m.family.name LIKE 'Galore'", MafiaFamily.class);
                List results = query.getResultList();
                int size = results.size();
                assertTrue("Incorrect number of results returned [" + size + "], expected [6]", size == 6);
            } catch (Exception e) {
                fail("Exception encountered on mulitple select statement (with tenant discriminator columns): " + e);
            }

            // Try a delete all on single table (Contracts)
            try {
                beginTransaction(em);
                int contracts = em.createNamedQuery("FindAllContracts").getResultList().size();
                int deletes = em.createNamedQuery("DeleteAllContracts").executeUpdate();
                assertTrue("Incorrect number of contracts deleted [" + deletes + "], expected [" + contracts + "]", deletes == 2);
                commitTransaction(em);
            } catch (Exception e) {
                fail("Exception encountered on delete all query with single table (with tenant discriminator columns): " + e);
            }

            if(getServerSession(getMULTI_TENANT_PU_123()).getPlatform().isSymfoware()) {
                getServerSession(getMULTI_TENANT_PU_123()).logMessage("Test AdvancedMultiTenantJunitTest partially skipped for this platform, "
                        +"Symfoware doesn't support UpdateAll/DeleteAll on multi-table objects (see rfe 298193).");
            } else {
                // Try a delete all on multiple table (MafiaFamily)
                try {
                    beginTransaction(em);
                    List<MafiaFamily> allFamilies = em.createNamedQuery("findAllMafiaFamilies").getResultList();
                    int families = allFamilies.size();
                    assertTrue("More than one family was found ["+ families +"]", families == 1);
                    Query deleteQuery = em.createNamedQuery("DeleteAllMafiaFamilies");
                    deleteQuery.setHint(QueryHints.ALLOW_NATIVE_SQL_QUERY, true);
                    int deletes = deleteQuery.executeUpdate();
                    assertTrue("Incorrect number of families deleted [" + deletes + "], expected [" + families + "]", deletes == 1);
                    commitTransaction(em);
                } catch (Exception e) {
                    fail("Exception encountered on delete all query with multiple table (with tenant discriminator columns): " + e);
                }
            }
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }

        // Some verification of what was deleted.
        EntityManager em007 = createSharedEMFEntityManager();
        try {
            List<MafiaFamily> families = em007.createNativeQuery("select * from JPA_MAFIA_FAMILY", MafiaFamily.class).getResultList();
            assertTrue("Incorrect number of families found through SQL [" + families.size() + "], expected [2]", families.size() == 2);

            // Clear out the shared cache with what we read through native SQL.
            clearCache(getMULTI_TENANT_PU());
            em007.clear();

            beginTransaction(em007);
            em007.setProperty("tenant.id", "007");
            em007.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "007");
            MafiaFamily family = em007.find(MafiaFamily.class, family007);
            assertFalse("Family 007 tags were nuked in delete all query above!", family.getTags().isEmpty());
            assertFalse("Family 007 revenue was nuked in delete all query above!", family.getRevenue() == null);
            commitTransaction(em007);
        } catch (Exception e) {
            fail("Exception caught: " + e);
        } finally {
            if (isTransactionActive(em007)) {
                rollbackTransaction(em007);
            }
            closeEntityManager(em007);
        }
    }

    /**
     * This test will try to DDL generate on deploy. Meaning once we try to
     * access the VPD PU we'll get an exception if we are not an oracle platform
     * so check before throwing an exception.
     *
     * VPD is currently supported only on Oracle.
     */
    public void testVPDEMPerTenant() {
        EntityManager em1 = null;
        EntityManager em2 = null;

        try {
            em1 = createVPDEntityManager();
            em1.setProperty("tenant.id", "bsmith@here.com");

            em2 = createVPDEntityManager();
            em2.setProperty("tenant.id", "gdune@there.ca");

            testInsertTask(em1, "blah", false);
            testInsertTask(em2, "halb", false);

            assertTrue("Found more than one result", em1.createQuery("Select t from Task t").getResultList().size() == 1);
            assertTrue("Found more than one result", em2.createQuery("Select t from Task t").getResultList().size() == 1);

            Task task1 = testInsertTaskWithOneSubtask(em1, "Rock that Propsal", false, "Write Proposal", false);
            assertNotNull(em1.find(Task.class, task1.getId()));
            assertNull(em2.find(Task.class, task1.getId())); // negative test

            Task task3 = testInsertTask(em2, "mow lawn", true);
            assertNull(em1.find(Task.class, task3.getId())); // negative test
            assertNotNull(em2.find(Task.class, task3.getId()));

            SubTask task4 = testInsertSubTaskObject(em1, "SubTask Object Creation", true);
            assertNotNull(em1.find(SubTask.class, task4.getId()));
            assertNull(em2.find(SubTask.class, task4.getId())); // negative test

        } catch (RuntimeException e) {
            if (em1 != null && isTransactionActive(em1)){
                rollbackTransaction(em1);
            }

            if (em2 != null && isTransactionActive(em2)){
                rollbackTransaction(em2);
            }

            if (! getPlatform(getMULTI_TENANT_VPD_PU()).isOracle()) {
                warning("VPD tests currently run only on an Oracle platform");
            } else {
                throw e;
            }
        } finally {
            if (em1 != null) {
                closeEntityManager(em1);
            }

            if (em2 != null) {
                closeEntityManager(em2);
            }
        }
    }

    public void testMultitenantPrimaryKeyWithIdClass() {
        EntityManager em = createSharedEMFEntityManager();

        PhoneNumber number = new PhoneNumber();;
        number.setAreaCode("613");
        number.setNumber("123-4567");
        number.setType("Home");

        try {
            beginTransaction(em);
            // On server side, you have to set the em properties after
            // transaction begins
            em.setProperty("tenant.id", "707");
            em.persist(number);
            commitTransaction(em);

            // This should hit the cache.
            beginTransaction(em);
            em.setProperty("tenant.id", "707");
            PhoneNumber refreshedNumber = em.find(PhoneNumber.class, number.buildPK());
            rollbackTransaction(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

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

    public void testMultitenantOneToOneReadObjectRead() {
        EntityManager em = createSharedEMFEntityManager();

        try {
            beginTransaction(em);

            em.setProperty("tenant.id", "371453");

            Envelope envelope = new Envelope();
            envelope.setColor("Red");

            Card card = new Card();
            card.setPrice(2.99);
            card.setColor("Yellow");
            card.setPrintYear(2012);
            card.setOccasion("Sympathy");
            card.setFrontCaption("Get well soon");
            card.setInsideCaption("Here's to a speedy recovery");

            card.setEnvelope(envelope);
            envelope.setCard(card);

            // Will cascade to the envelope
            em.persist(card);
            commitTransaction(em);

            em.clear();
            clearCache(getMULTI_TENANT_PU());

            beginTransaction(em);
            em.setProperty("tenant.id", "371453");
            envelope = em.merge(envelope);
            commitTransaction(em);

            em.clear();
            clearCache(getMULTI_TENANT_PU());

            beginTransaction(em);
            em.setProperty("tenant.id", "371453");
            card = em.merge(card);
            commitTransaction(em);

        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }

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

    private Task testInsertTask(EntityManager em, String description, boolean isCompleted) {
        beginTransaction(em);
        Task task = new Task();
        task.setDescription(description);
        task.setCompleted(isCompleted);
        em.persist(task);
        commitTransaction(em);
        return task;
    }

    private Task testInsertTaskWithOneSubtask(EntityManager em, String description, boolean isCompleted, String subtaskDesc, boolean isSubtaskCompleted) {
        beginTransaction(em);
        Task task = new Task();
        Task subtask = new Task();
        task.setDescription(description);
        task.setCompleted(isCompleted);
        subtask.setDescription(subtaskDesc);
        subtask.setCompleted(isSubtaskCompleted);
        task.addSubtask(subtask);
        em.persist(subtask);
        em.persist(task);
        commitTransaction(em);
        return task;
    }

    private SubTask testInsertSubTaskObject(EntityManager em, String description, boolean isCompleted) {
        beginTransaction(em);
        SubTask subTask = new SubTask();
        subTask.setDescription(description);
        subTask.setCompleted(isCompleted);
        em.persist(subTask);
        commitTransaction(em);
        return subTask;
    }
}
