/*
 * Copyright (c) 2012, 2018 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 v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation
//     03/29/2016-2.6_WAS Will Dazey
//       - 490114: Add testing for PersistenceUnitUtil.getIdentifier with nested embeddables in EmbeddedId class
package org.eclipse.persistence.testing.tests.jpa.complexaggregate;

import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.Query;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.eclipse.persistence.annotations.BatchFetchType;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker;
import org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.jpa.JpaHelper;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.testing.framework.JoinedAttributeTestHelper;
import org.eclipse.persistence.testing.framework.QuerySQLTracker;
import org.eclipse.persistence.testing.framework.TestErrorException;
import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Body;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.CitySlicker;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.CoachVitals;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.ComplexAggregateTableCreator;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.CountryDweller;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Heart;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.HockeyCoach;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.HockeyPlayer;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.HockeyPuck;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.HockeyRink;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.HockeySponsor;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.HockeyTeam;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Name;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.PersonalVitals;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Location;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Place;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Role;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.TeamVitals;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Torso;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.Vitals;
import org.eclipse.persistence.testing.models.jpa.complexaggregate.World;

/**
 * <p><b>Purpose</b>: To JPA with the complex aggregate test model.
 */
public class ComplexAggregateTestSuite extends JUnitTestCase {

    public ComplexAggregateTestSuite() {
    }

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

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

    public static Test suite() {
        TestSuite suite = (TestSuite) suiteSpring();
        suite.addTest(new ComplexAggregateTestSuite("testInvalidateAndRefreshEmbeddableParent"));

        return suite;
    }

    public static Test suiteSpring() {
        TestSuite suite = new TestSuite();
        suite.setName("ComplexAggregateTestSuite");
        suite.addTest(new ComplexAggregateTestSuite("testSetup"));
        suite.addTest(new ComplexAggregateTestSuite("testMapKeyColumnMerge"));
        suite.addTest(new ComplexAggregateTestSuite("testAggregatePrimaryKey"));
        suite.addTest(new ComplexAggregateTestSuite("testAggregatePrimaryKeyOrderBy"));
        suite.addTest(new ComplexAggregateTestSuite("testNestedAggregate"));
        suite.addTest(new ComplexAggregateTestSuite("testNestedAggregatePrimaryKey"));
        suite.addTest(new ComplexAggregateTestSuite("testAggregateReadOnlyMapKey"));
        suite.addTest(new ComplexAggregateTestSuite("testComplexAggregateJoin"));
        suite.addTest(new ComplexAggregateTestSuite("testComplexAggregateBatch"));
        suite.addTest(new ComplexAggregateTestSuite("testAggregateFieldAttributeOverrides"));

        return suite;
    }

    public void testMapKeyColumnMerge() {
        clearCache();

        EntityManager em = createEntityManager();
        try {
            HockeyPlayer player = new HockeyPlayer();
            player.setFirstName("Guy");
            player.setLastName("Flower");
            // must have non null Vitals and TeamVitals because TeamVitals has a target foreign key mapping, therefore allowingNull automatically set to false
            Vitals vitals = new Vitals();
            vitals.setTeamVitals(new TeamVitals());
            player.setVitals(vitals);

            HockeyPlayer player2 = new HockeyPlayer();
            player2.setFirstName("Power");
            player2.setLastName("Flower");;
            // must have non null Vitals and TeamVitals because TeamVitals has a target foreign key mapping, therefore allowingNull automatically set to false
            Vitals vitals2 = new Vitals();
            vitals2.setTeamVitals(new TeamVitals());
            player2.setVitals(vitals2);

            HockeyCoach coach = new HockeyCoach();
            coach.setFirstName("Scott");
            coach.setLastName("Arrowman");
            CoachVitals cVitals = new CoachVitals();
            PersonalVitals pVitals = new PersonalVitals();
            pVitals.setAge(45);
            pVitals.setHeight(3.3);
            pVitals.setWeight(333);
            cVitals.setPersonalVitals(pVitals);
            coach.setVitals(cVitals);

            beginTransaction(em);

            em.persist(player);
            em.persist(player2);
            em.persist(coach);
            Object playerId = player.getPlayerId();
            Object player2Id = player2.getPlayerId();
            Object coachId = coach.getId();
            coach.addFavouritePlayer(player);

            commitTransaction(em);
            closeEntityManager(em);

            em = createEntityManager();
            beginTransaction(em);
    //        ((ChangeTracker)coach)._persistence_setPropertyChangeListener(null);
    //        ((IndirectMap)coach.getFavouritePlayers())._persistence_setPropertyChangeListener(null);
            coach.addFavouritePlayer(player2);
            em.merge(coach);
            commitTransaction(em);

            beginTransaction(em);
            player = em.find(HockeyPlayer.class, playerId);
            player2 = em.find(HockeyPlayer.class, player2Id);
            coach = em.find(HockeyCoach.class, coachId);
            if (player != null){
                em.remove(player);
            }
            if (player2 != null){
                em.remove(player2);
            }
            if (coach != null){
                em.remove(coach);
            }
            commitTransaction(em);
        } finally {
            closeEntityManagerAndTransaction(em);
        }
    }

    public void testAggregatePrimaryKey() {
        clearCache();
        EntityManager em = createEntityManager();
        try {
            Name name1 = new Name();
            name1.setFirstName("Tom");
            name1.setLastName("Ware");

            CountryDweller countryDweller = new CountryDweller();
            countryDweller.setAge(30);
            countryDweller.setName(name1);

            CitySlicker citySlicker = new CitySlicker();
            citySlicker.setAge(53);
            // Bug 300696 - Invalid tests: sharing embedded objects is not allowed
            // Changed the test to use clone instead of sharing the same Name between the two Entities.
            Name name1Clone = (Name)name1.clone();
            citySlicker.setName(name1Clone);

            Name name2 = new Name();
            name2.setFirstName("Guy");
            name2.setLastName("Pelletier");

            CountryDweller countryDweller2 = new CountryDweller();
            countryDweller2.setAge(65);
            countryDweller2.setName(name2);

            beginTransaction(em);
            em.persist(countryDweller);
            em.persist(countryDweller2);
            em.persist(citySlicker);
            commitTransaction(em);

            // Clear the cache.
            clearCache();
            em.clear();

            // Now read them back in and delete them.
            beginTransaction(em);

            // Note that in Identity case name1Clone may no longer have the same id as name1
            CitySlicker cs = em.find(CitySlicker.class, name1Clone);
            CountryDweller cd = em.merge(countryDweller);
            CountryDweller cd2 = em.merge(countryDweller2);

            em.remove(cs);
            em.remove(cd);
            em.remove(cd2);

            commitTransaction(em);
        } catch (RuntimeException e) {
            closeEntityManagerAndTransaction(em);
            throw e;
        }
    }

    public void testAggregatePrimaryKeyOrderBy() {
        clearCache();
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);

            World world = new World();

            // First pair
            Name name1 = new Name();
            name1.setFirstName("Guy");
            name1.setLastName("Pelletier");

            CountryDweller countryDweller1 = new CountryDweller();
            countryDweller1.setAge(30);
            countryDweller1.setName(name1);
            countryDweller1.setGender("Male");
            em.persist(countryDweller1);
            world.addCountryDweller(countryDweller1);

            CitySlicker citySlicker1 = new CitySlicker();
            citySlicker1.setAge(53);
            // Bug 300696 - Invalid tests: sharing embedded objects is not allowed
            // Changed the test to use clone instead of sharing the same Name between the two Entities.
            Name name1Clone = (Name)name1.clone();
            citySlicker1.setName(name1Clone);
            citySlicker1.setGender("Male");
            em.persist(citySlicker1);
            world.addCitySlicker(citySlicker1);

            // Second pair
            Name name2 = new Name();
            name2.setFirstName("Steve");
            name2.setLastName("Harp");

            CountryDweller countryDweller2 = new CountryDweller();
            countryDweller2.setAge(28);
            countryDweller2.setName(name2);
            countryDweller2.setGender("Male");
            em.persist(countryDweller2);
            world.addCountryDweller(countryDweller2);

            CitySlicker citySlicker2 = new CitySlicker();
            citySlicker2.setAge(41);
            // Bug 300696 - Invalid tests: sharing embedded objects is not allowed
            // Changed the test to use clone instead of sharing the same Name between the two Entities.
            Name name2Clone = (Name)name2.clone();
            citySlicker2.setName(name2Clone);
            citySlicker2.setGender("Male");
            em.persist(citySlicker2);
            world.addCitySlicker(citySlicker2);

            // Third pair
            Name name3 = new Name();
            name3.setFirstName("Jesse");
            name3.setLastName("Petoncle");

            CountryDweller countryDweller3 = new CountryDweller();
            countryDweller3.setAge(48);
            countryDweller3.setName(name3);
            countryDweller3.setGender("Male");
            em.persist(countryDweller3);
            world.addCountryDweller(countryDweller3);

            CitySlicker citySlicker3 = new CitySlicker();
            citySlicker3.setAge(76);
            // Bug 300696 - Invalid tests: sharing embedded objects is not allowed
            // Changed the test to use clone instead of sharing the same Name between the two Entities.
            Name name3Clone = (Name)name3.clone();
            citySlicker3.setName(name3Clone);
            citySlicker3.setGender("Female");
            em.persist(citySlicker3);
            world.addCitySlicker(citySlicker3);

            em.persist(world);

            commitTransaction(em);

            // Clear the cache.
            clearCache();
            em.clear();

            // Now read them back in and delete them.
            beginTransaction(em);

            World w = em.find(World.class, world.getId());

            Collection css = w.getCitySlickers();
            css.toString();
            Collection cds = w.getCountryDwellers();
            cds.toString();

            // Check the ordering
            // JBS - Ordering check removed as order is random based on class method order which is not consistent in Java.
            //citySlickersAreOrdered = ((CitySlicker) css.elementAt(0)).getName().getFirstName().equals("Guy") && ((CitySlicker) css.elementAt(1)).getName().getFirstName().equals("Jesse") && ((CitySlicker) css.elementAt(2)).getName().getFirstName().equals("Steve");
            //countryDwellersAreOrdered = ((CountryDweller) cds.elementAt(0)).getAge() == 28 && ((CountryDweller) cds.elementAt(1)).getAge() == 30 && ((CountryDweller) cds.elementAt(2)).getAge() == 48;

            // Make sure we delete them
            // Note that in Identity case name1Clone may no longer have the same id as name1
            CitySlicker cs1 = em.find(CitySlicker.class, name1Clone);
            em.remove(cs1);
            CitySlicker cs2 = em.find(CitySlicker.class, name2Clone);
            em.remove(cs2);
            CitySlicker cs3 = em.find(CitySlicker.class, name3Clone);
            em.remove(cs3);

            CountryDweller cd1 = em.merge(countryDweller1);
            em.remove(cd1);
            CountryDweller cd2 = em.merge(countryDweller2);
            em.remove(cd2);
            CountryDweller cd3 = em.merge(countryDweller3);
            em.remove(cd3);

            commitTransaction(em);

        } catch (RuntimeException e) {
            closeEntityManagerAndTransaction(em);
            throw e;
        }
    }

    public void testNestedAggregate() {
        int[] teamIDs = new int[3];
        clearCache();
        //////// Team 1 ////////
        HockeyTeam team1 = new HockeyTeam();
        team1.setName("Axemen");
        team1.setAwayColor("Red");
        team1.setHomeColor("White");
        team1.setLevel("Division 5");

        //////// Team 2 ////////
        HockeyTeam team2 = new HockeyTeam();
        team2.setName("Cartier Partners");
        team2.setAwayColor("Black");
        team2.setHomeColor("White");
        team2.setLevel("Division 2");

        //////// Team 3 ////////
        HockeyTeam team3 = new HockeyTeam();
        team3.setName("Dead Last");
        team3.setAwayColor("Blue");
        team3.setHomeColor("White");
        team3.setLevel("Division 4");

        //////// Player 1 ////////
        HockeyPlayer player1 = new HockeyPlayer();
        player1.setFirstName("Guy");
        player1.setLastName("Pelletier");

        PersonalVitals personalVitals1 = new PersonalVitals();
        personalVitals1.setAge(29);
        personalVitals1.setHeight(1.80);
        personalVitals1.setWeight(180);

        TeamVitals teamVitals1 = new TeamVitals();
        teamVitals1.setHockeyTeam(team2);
        team2.getPlayers().add(player1);
        teamVitals1.setJerseyNumber(20);
        teamVitals1.setPosition("Goalie");
        teamVitals1.getRoles().add(new Role("Stop pucks!"));

        Vitals vitals1 = new Vitals();
        vitals1.setPersonalVitals(personalVitals1);
        vitals1.setTeamVitals(teamVitals1);
        player1.setVitals(vitals1);

        //////// Player 2 ////////
        HockeyPlayer player2 = new HockeyPlayer();
        player2.setFirstName("Dave");
        player2.setLastName("McCann");

        PersonalVitals personalVitals2 = new PersonalVitals();
        personalVitals2.setAge(35);
        personalVitals2.setHeight(1.77);
        personalVitals2.setWeight(165);

        TeamVitals teamVitals2 = new TeamVitals();
        teamVitals2.setHockeyTeam(team1);
        team1.getPlayers().add(player2);
        teamVitals2.setJerseyNumber(70);
        teamVitals2.setPosition("Left wing");
        teamVitals2.getRoles().add(new Role("Power play unit"));
        teamVitals2.getRoles().add(new Role("Face-off specialist"));

        Vitals vitals2 = new Vitals();
        vitals2.setPersonalVitals(personalVitals2);
        vitals2.setTeamVitals(teamVitals2);
        player2.setVitals(vitals2);

        //////// Player 3 ////////
        HockeyPlayer player3 = new HockeyPlayer();
        player3.setFirstName("Tom");
        player3.setLastName("Ware");

        PersonalVitals personalVitals3 = new PersonalVitals();
        personalVitals3.setAge(30);
        personalVitals3.setHeight(1.83);
        personalVitals3.setWeight(200);

        TeamVitals teamVitals3 = new TeamVitals();
        teamVitals3.setHockeyTeam(team1);
        team1.getPlayers().add(player3);
        teamVitals3.setJerseyNumber(12);
        teamVitals3.setPosition("Defence");
        teamVitals3.getRoles().add(new Role("Penalty kill unit"));

        Vitals vitals3 = new Vitals();
        vitals3.setPersonalVitals(personalVitals3);
        vitals3.setTeamVitals(teamVitals3);
        player3.setVitals(vitals3);

        //////// Player 4 ////////
        HockeyPlayer player4 = new HockeyPlayer();
        player4.setFirstName("George");
        player4.setLastName("Robinson");

        PersonalVitals personalVitals4 = new PersonalVitals();
        personalVitals4.setAge(32);
        personalVitals4.setHeight(1.86);
        personalVitals4.setWeight(210);

        TeamVitals teamVitals4 = new TeamVitals();
        teamVitals4.setHockeyTeam(team3);
        team3.getPlayers().add(player4);
        teamVitals4.setJerseyNumber(6);
        teamVitals4.setPosition("Center");
        teamVitals4.getRoles().add(new Role("Power play unit"));
        teamVitals4.getRoles().add(new Role("Goon"));

        Vitals vitals4 = new Vitals();
        vitals4.setPersonalVitals(personalVitals4);
        vitals4.setTeamVitals(teamVitals4);
        player4.setVitals(vitals4);

        //////// Player 5 ////////
        HockeyPlayer player5 = new HockeyPlayer();
        player5.setFirstName("Andrew");
        player5.setLastName("Glennie");

        PersonalVitals personalVitals5 = new PersonalVitals();
        personalVitals5.setAge(31);
        personalVitals5.setHeight(1.80);
        personalVitals5.setWeight(205);

        TeamVitals teamVitals5 = new TeamVitals();
        teamVitals5.setHockeyTeam(team3);
        team3.getPlayers().add(player5);
        teamVitals5.setJerseyNumber(7);
        teamVitals5.setPosition("Right wing");

        Vitals vitals5 = new Vitals();
        vitals5.setPersonalVitals(personalVitals5);
        vitals5.setTeamVitals(teamVitals5);
        player5.setVitals(vitals5);

        //////// Player 6 ////////
        HockeyPlayer player6 = new HockeyPlayer();
        player6.setFirstName("David");
        player6.setLastName("Whittaker");

        PersonalVitals personalVitals6 = new PersonalVitals();
        personalVitals6.setAge(32);
        personalVitals6.setHeight(1.83);
        personalVitals6.setWeight(190);

        TeamVitals teamVitals6 = new TeamVitals();
        teamVitals6.setHockeyTeam(team2);
        team2.getPlayers().add(player6);
        teamVitals6.setJerseyNumber(17);
        teamVitals6.setPosition("Defence");

        Vitals vitals6 = new Vitals();
        vitals6.setPersonalVitals(personalVitals6);
        vitals6.setTeamVitals(teamVitals6);
        player6.setVitals(vitals6);

        //////// Coach 1 ////////
        HockeyCoach coach1 = new HockeyCoach();
        coach1.setFirstName("Don");
        coach1.setLastName("Hyslop");

        PersonalVitals coachPersonalVitals1 = new PersonalVitals();
        coachPersonalVitals1.setAge(55);
        coachPersonalVitals1.setHeight(1.85);
        coachPersonalVitals1.setWeight(200);

        CoachVitals coachVitals1 = new CoachVitals();
        coachVitals1.setPersonalVitals(coachPersonalVitals1);
        coachVitals1.setHockeyTeam(team1);
        team1.getCoaches().add(coach1);
        coach1.setVitals(coachVitals1);

        //////// Coach 2 ////////
        HockeyCoach coach2 = new HockeyCoach();
        coach2.setFirstName("Al");
        coach2.setLastName("Montoya");

        PersonalVitals coachPersonalVitals2 = new PersonalVitals();
        coachPersonalVitals2.setAge(63);
        coachPersonalVitals2.setHeight(1.86);
        coachPersonalVitals2.setWeight(213);

        CoachVitals coachVitals2 = new CoachVitals();
        coachVitals2.setPersonalVitals(coachPersonalVitals2);
        coachVitals2.setHockeyTeam(team2);
        team2.getCoaches().add(coach2);
        coach2.setVitals(coachVitals2);

        //////// Coach 3 ////////
        HockeyCoach coach3 = new HockeyCoach();
        coach3.setFirstName("Guy");
        coach3.setLastName("Carbonneau");

        PersonalVitals coachPersonalVitals3 = new PersonalVitals();
        coachPersonalVitals3.setAge(47);
        coachPersonalVitals3.setHeight(1.91);
        coachPersonalVitals3.setWeight(191);

        CoachVitals coachVitals3 = new CoachVitals();
        coachVitals3.setPersonalVitals(coachPersonalVitals3);
        coachVitals3.setHockeyTeam(team3);
        team3.getCoaches().add(coach3);
        coach3.setVitals(coachVitals3);

        //////// Coach 4 ////////
        HockeyCoach coach4 = new HockeyCoach();
        coach4.setFirstName("Walter");
        coach4.setLastName("Mullen");

        PersonalVitals coachPersonalVitals4 = new PersonalVitals();
        coachPersonalVitals4.setAge(67);
        coachPersonalVitals4.setHeight(1.94);
        coachPersonalVitals4.setWeight(187);

        CoachVitals coachVitals4 = new CoachVitals();
        coachVitals4.setPersonalVitals(coachPersonalVitals4);
        coachVitals4.setHockeyTeam(team1);
        team1.getCoaches().add(coach4);
        coach4.setVitals(coachVitals4);

        //////// Coach 5 ////////
        HockeyCoach coach5 = new HockeyCoach();
        coach5.setFirstName("Ben");
        coach5.setLastName("Gasket");

        PersonalVitals coachPersonalVitals5 = new PersonalVitals();
        coachPersonalVitals5.setAge(33);
        coachPersonalVitals5.setHeight(1.67);
        coachPersonalVitals5.setWeight(155);

        CoachVitals coachVitals5 = new CoachVitals();
        coachVitals5.setPersonalVitals(coachPersonalVitals5);
        coachVitals5.setHockeyTeam(team2);
        team2.getCoaches().add(coach5);
        coach5.setVitals(coachVitals5);

        //////// Coach 6 ////////
        HockeyCoach coach6 = new HockeyCoach();
        coach6.setFirstName("Jim");
        coach6.setLastName("Balogna");

        PersonalVitals coachPersonalVitals6 = new PersonalVitals();
        coachPersonalVitals6.setAge(37);
        coachPersonalVitals6.setHeight(1.77);
        coachPersonalVitals6.setWeight(179);

        CoachVitals coachVitals6 = new CoachVitals();
        coachVitals6.setPersonalVitals(coachPersonalVitals5);
        coachVitals6.setHockeyTeam(team3);
        team3.getCoaches().add(coach6);
        coach6.setVitals(coachVitals6);

        EntityManager em = createEntityManager();
        //////// Commit the new objects ////////
        beginTransaction(em);

        try{
            em.persist(team1);
            teamIDs[0] = team1.getId();
            em.persist(team2);
            teamIDs[1] = team2.getId();
            em.persist(team3);
            teamIDs[2] = team3.getId();

            em.persist(player1);
            em.persist(player2);
            em.persist(player3);
            em.persist(player4);
            em.persist(player5);
            em.persist(player6);

            em.persist(coach1);
            em.persist(coach2);
            em.persist(coach3);
            em.persist(coach4);
            em.persist(coach5);
            em.persist(coach6);

            commitTransaction(em);
        } catch (RuntimeException ex) {
            closeEntityManagerAndTransaction(em);
            throw ex;
        }

        em = createEntityManager();
        // Check the cache for the objects
        checkTeam(teamIDs[0], em);
        checkTeam(teamIDs[1], em);
        checkTeam(teamIDs[2], em);

        // Initialize the identity map to make sure they were persisted
        clearCache();

        em = createEntityManager();
        em.clear();

        checkTeam(teamIDs[0], em);
        checkTeam(teamIDs[1], em);
        checkTeam(teamIDs[2], em);

        // Verify the order by on coaches worked for team 1 (teamIDs[0])
        HockeyTeam team = em.find(HockeyTeam.class, teamIDs[0]);

        if (team.getCoaches().get(0).getVitals().getPersonalVitals().getAge() != 67) {
            throw new TestErrorException("The order by specification on age for hockey coaches was not observed: " + team.getCoaches());
        }
    }

    private void checkTeam(int id, EntityManager em) {
        HockeyTeam team = em.find(HockeyTeam.class, id);
        if (team == null){
            throw new TestErrorException("Hockey team with ID: " + id + ", was not created.");
        }

        if (team.getPlayers().size() != 2) {
            throw new TestErrorException("Hockey team with ID: " + id + ", did not have 2 players, had: " + team.getPlayers().size());
        }

        if (team.getCoaches().size() != 2) {
            throw new TestErrorException("Hockey team with ID: " + id + ", did not have 2 coach, had: " + team.getCoaches().size());
        }
    }

    public void testNestedAggregatePrimaryKey() {
        clearCache();

        Torso torso;

        EntityManagerFactory emf = getEntityManagerFactory();
        EntityManager em = createEntityManager();
        try {
            Body body = new Body();
            torso = new Torso();
            Heart heart = new Heart();
            heart.setSize(8);
            torso.setHeart(heart);
            body.setTorso(torso);

            beginTransaction(em);
            em.persist(body);
            commitTransaction(em);
        } catch (Exception e) {
            throw new TestErrorException("Exception caught when persisting the new body: " + e.getMessage());
        } finally {
            closeEntityManager(em);
        }

        em = createEntityManager();
        Exception m_testException = null;
        DatabaseSessionImpl m_session = getDatabaseSession();
        Body m_refreshedBody = null;
        // Try to read the body back, clear the cache first.
        try {
            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();
            m_refreshedBody = em.find(Body.class, torso);
        } catch (Exception e) {
            m_testException = e;
        }

        if (m_testException != null) {
            throw new TestErrorException("Exception caught reading back the persisted body: " + m_testException);
        }

        if (m_refreshedBody == null) {
            throw new TestErrorException("Unable to read back the persisted body");
        }

        ClassDescriptor descriptor = getDatabaseSession().getClassDescriptor(Body.class);
        Object pks = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(m_refreshedBody, m_session);
        Torso createdTorso = (Torso) descriptor.getCMPPolicy().createPrimaryKeyInstanceFromId(pks, m_session);
        assertTrue("PK's do not match.", m_refreshedBody.getTorso().equals(createdTorso));

        PersistenceUnitUtil util = emf.getPersistenceUnitUtil();
        assertTrue("PersistenceUnitUtil returned incorrect identifier", torso.equals(util.getIdentifier(m_refreshedBody)));
    }

    public void testAggregateReadOnlyMapKey() {
        clearCache();

        HockeyPlayer player = new HockeyPlayer();
        player.setFirstName("Guy");
        player.setLastName("Flower");

        PersonalVitals personalVitals2 = new PersonalVitals();
        personalVitals2.setAge(45);
        personalVitals2.setHeight(1.80);
        personalVitals2.setWeight(185);

        TeamVitals teamVitals2 = new TeamVitals();
        teamVitals2.setJerseyNumber(70);
        teamVitals2.setPosition("Right wing");

        Vitals vitals2 = new Vitals();
        vitals2.setPersonalVitals(personalVitals2);
        vitals2.setTeamVitals(teamVitals2);
        player.setVitals(vitals2);

        HockeyCoach coach = new HockeyCoach();
        coach.setFirstName("Scott");
        coach.setLastName("Arrowman");

        PersonalVitals coachPersonalVitals1 = new PersonalVitals();
        coachPersonalVitals1.setAge(65);
        coachPersonalVitals1.setHeight(1.85);
        coachPersonalVitals1.setWeight(200);

        CoachVitals coachVitals1 = new CoachVitals();
        coachVitals1.setPersonalVitals(coachPersonalVitals1);
        coach.setVitals(coachVitals1);

        EntityManager em = createEntityManager();
        beginTransaction(em);
        try {
            em.persist(player);
            em.persist(coach);
            commitTransaction(em);

            Object playerId = player.getPlayerId();
            Object coachId = coach.getId();

            clearCache();
            em.clear();

            beginTransaction(em);
            player = em.find(HockeyPlayer.class, player.getPlayerId());
            coach = em.find(HockeyCoach.class, coach.getId());

            coach.addFavouritePlayer(player);
            commitTransaction(em);

            beginTransaction(em);
            player = em.find(HockeyPlayer.class, playerId);
            coach = em.find(HockeyCoach.class, coachId);
            if (player != null){
                em.remove(player);
            }
            if (coach != null){
                em.remove(coach);
            }
            commitTransaction(em);
        } finally {
            closeEntityManagerAndTransaction(em);
        }
    }

    public void testComplexAggregateJoin() {
        clearCache();

        HockeyTeam team1 = new HockeyTeam();
        team1.setName("Axemen");
        team1.setAwayColor("Red");
        team1.setHomeColor("White");
        team1.setLevel("Division 5");

        ////////Player 1 ////////
        HockeyPlayer player1 = new HockeyPlayer();
        player1.setFirstName("Guy");
        player1.setLastName("Pelletier");

        PersonalVitals personalVitals1 = new PersonalVitals();
        personalVitals1.setAge(29);
        personalVitals1.setHeight(1.80);
        personalVitals1.setWeight(180);

        TeamVitals teamVitals1 = new TeamVitals();

        teamVitals1.setJerseyNumber(20);
        teamVitals1.setPosition("Goalie");
        teamVitals1.getRoles().add(new Role("Stop pucks!"));

        Vitals vitals1 = new Vitals();
        vitals1.setPersonalVitals(personalVitals1);
        vitals1.setTeamVitals(teamVitals1);
        player1.setVitals(vitals1);


        ////////Coach 1 ////////
        HockeyCoach coach1 = new HockeyCoach();
        coach1.setFirstName("Don");
        coach1.setLastName("Hyslop");

        PersonalVitals coachPersonalVitals1 = new PersonalVitals();
        coachPersonalVitals1.setAge(55);
        coachPersonalVitals1.setHeight(1.85);
        coachPersonalVitals1.setWeight(200);

        CoachVitals coachVitals1 = new CoachVitals();
        coachVitals1.setPersonalVitals(coachPersonalVitals1);
        coach1.setVitals(coachVitals1);

        ////////persist the new objects ////////

        EntityManager em = createEntityManager();
        beginTransaction(em);
        em.persist(team1);

        em.persist(player1);
        em.persist(coach1);

        //setup relationships:
        teamVitals1.setHockeyTeam(team1);
        team1.getPlayers().add(player1);
        team1.getCoaches().add(coach1);
        coachVitals1.setHockeyTeam(team1);

        em.flush();

        DatabaseSessionImpl m_session = getDatabaseSession();
        List hockeyCoachResults = null;
        List controledCoachList =  null;
        List controledPlayerList = null;
        List hockeyPlayerResults = null;
        try {
            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            Query query1 = em.createQuery("Select a from HockeyCoach a where a.id = "+coach1.getId());
            query1.setHint(QueryHints.LEFT_FETCH, "a.vitals.hockeyTeam");
            hockeyCoachResults = query1.getResultList();

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            Query query2 = em.createQuery("Select z from HockeyPlayer z where z.playerId = "+player1.getPlayerId());
            //query2.setHint(QueryHints.LEFT_FETCH, "z.coach");
            query2.setHint(QueryHints.LEFT_FETCH, "z.vitals.teamVitals.hockeyTeam.coaches");
            hockeyPlayerResults = query2.getResultList();

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            HockeyCoach expectedCoach = em.find(HockeyCoach.class, coach1.getId());
            expectedCoach.getVitals().getHockeyTeam();
            controledCoachList = new ArrayList<HockeyCoach>(1);
            controledCoachList.add(expectedCoach);

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            HockeyPlayer expectedPlayer = em.find(HockeyPlayer.class, player1.getPlayerId());
            expectedPlayer.getVitals().getTeamVitals().getHockeyTeam().getCoaches().size();
            controledPlayerList = new ArrayList<HockeyPlayer>(1);
            controledPlayerList.add(expectedPlayer);

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();
        } finally  {
            try {
                closeEntityManagerAndTransaction(em);
            } catch (RuntimeException re){
                //ignore
            }
        }

        String errorMsg = JoinedAttributeTestHelper.compareCollections(controledCoachList, hockeyCoachResults, m_session.getClassDescriptor(HockeyCoach.class), m_session);
        if (errorMsg.length() > 0) {
            throw new TestErrorException(errorMsg);
        }

        errorMsg = JoinedAttributeTestHelper.compareCollections(controledPlayerList, hockeyPlayerResults, m_session.getClassDescriptor(HockeyPlayer.class), m_session);
        if (errorMsg.length() > 0) {
            throw new TestErrorException(errorMsg);
        }
    }

    public void testComplexAggregateBatch() {
        clearCache();

        HockeyTeam team1 = new HockeyTeam();
        team1.setName("Axemen");
        team1.setAwayColor("Red");
        team1.setHomeColor("White");
        team1.setLevel("Division 5");

        HockeyTeam team2 = new HockeyTeam();
        team2.setName("Ducks");
        team2.setAwayColor("Black");
        team2.setHomeColor("Red");
        team2.setLevel("Division 4");

        ////////Player 1 ////////
        HockeyPlayer player1 = new HockeyPlayer();
        player1.setFirstName("Guy");
        player1.setLastName("Pelletier");

        PersonalVitals personalVitals1 = new PersonalVitals();
        personalVitals1.setAge(29);
        personalVitals1.setHeight(1.80);
        personalVitals1.setWeight(180);

        TeamVitals teamVitals1 = new TeamVitals();

        teamVitals1.setJerseyNumber(20);
        teamVitals1.setPosition("Goalie");
        teamVitals1.getRoles().add(new Role("Stop pucks!"));

        Vitals vitals1 = new Vitals();
        vitals1.setPersonalVitals(personalVitals1);
        vitals1.setTeamVitals(teamVitals1);
        player1.setVitals(vitals1);


        ////////Coach 1 ////////
        HockeyCoach coach1 = new HockeyCoach();
        coach1.setFirstName("Don");
        coach1.setLastName("Hyslop");

        PersonalVitals coachPersonalVitals1 = new PersonalVitals();
        coachPersonalVitals1.setAge(55);
        coachPersonalVitals1.setHeight(1.85);
        coachPersonalVitals1.setWeight(200);

        CoachVitals coachVitals1 = new CoachVitals();
        coachVitals1.setPersonalVitals(coachPersonalVitals1);
        coach1.setVitals(coachVitals1);

        ////////Coach 2 ////////
        HockeyCoach coach2 = new HockeyCoach();
        coach2.setFirstName("Bob");
        coach2.setLastName("Jones");

        PersonalVitals coachPersonalVitals2 = new PersonalVitals();
        coachPersonalVitals2.setAge(44);
        coachPersonalVitals2.setHeight(1.95);
        coachPersonalVitals2.setWeight(250);

        CoachVitals coachVitals2 = new CoachVitals();
        coachVitals2.setPersonalVitals(coachPersonalVitals2);
        coach2.setVitals(coachVitals2);

        ////////persist the new objects ////////

        DatabaseSessionImpl m_session = getDatabaseSession();

        // Count SQL.
        QuerySQLTracker counter = new QuerySQLTracker(m_session);

        EntityManager em = createEntityManager();

        beginTransaction(em);
        em.persist(team1);
        em.persist(team2);

        em.persist(player1);
        em.persist(coach1);
        em.persist(coach2);

        //setup relationships:
        teamVitals1.setHockeyTeam(team1);
        team1.getPlayers().add(player1);
        team1.getCoaches().add(coach1);
        team2.getCoaches().add(coach2);
        coachVitals1.setHockeyTeam(team1);
        coachVitals2.setHockeyTeam(team2);

        em.flush();
        try {
            counter.getSqlStatements().clear();

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            Query query1 = em.createQuery("Select a from HockeyCoach a");
            query1.setHint(QueryHints.BATCH, "a.vitals.hockeyTeam");
            List<HockeyCoach> hockeyCoachResults = query1.getResultList();
            for (HockeyCoach coach : hockeyCoachResults) {
                coach.getVitals().getHockeyTeam();
            }

            if (isWeavingEnabled() && counter.getSqlStatements().size() != 2) {
                fail("Should have been 2 queries but was: " + counter.getSqlStatements().size());
            }

            counter.getSqlStatements().clear();

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            query1 = em.createQuery("Select a from HockeyCoach a");
            query1.setHint(QueryHints.BATCH, "a.vitals.hockeyTeam");
            query1.setHint(QueryHints.BATCH_TYPE, BatchFetchType.IN);
            hockeyCoachResults = query1.getResultList();
            for (HockeyCoach coach : hockeyCoachResults) {
                coach.getVitals().getHockeyTeam();
            }

            if (isWeavingEnabled() && counter.getSqlStatements().size() != 2) {
                fail("Should have been 2 queries but was: " + counter.getSqlStatements().size());
            }

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            counter.getSqlStatements().clear();

            query1 = em.createQuery("Select a from HockeyCoach a");
            query1.setHint(QueryHints.BATCH, "a.vitals.hockeyTeam");
            query1.setHint(QueryHints.BATCH_TYPE, BatchFetchType.EXISTS);
            hockeyCoachResults = query1.getResultList();
            for (HockeyCoach coach : hockeyCoachResults) {
                coach.getVitals().getHockeyTeam();
            }

            if (isWeavingEnabled() && counter.getSqlStatements().size() != 2) {
                fail("Should have been 2 queries but was: " + counter.getSqlStatements().size());
            }

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            Query query2 = em.createQuery("Select z from HockeyPlayer z where z.playerId <> 0");
            query2.setHint(QueryHints.BATCH, "z.vitals.teamVitals.hockeyTeam.coaches");
            List<HockeyPlayer> hockeyPlayerResults = query2.getResultList();
            for (HockeyPlayer player : hockeyPlayerResults) {
                player.getVitals().getTeamVitals().getHockeyTeam().getCoaches();
            }

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            query2 = em.createQuery("Select z from HockeyPlayer z where z.playerId <> 0");
            query2.setHint(QueryHints.BATCH, "z.vitals.teamVitals.hockeyTeam.coaches");
            query2.setHint(QueryHints.BATCH_TYPE, BatchFetchType.IN);
            hockeyPlayerResults = query2.getResultList();
            for (HockeyPlayer player : hockeyPlayerResults) {
                player.getVitals().getTeamVitals().getHockeyTeam().getCoaches();
            }

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();

            query2 = em.createQuery("Select z from HockeyPlayer z where z.playerId <> 0");
            query2.setHint(QueryHints.BATCH, "z.vitals.teamVitals.hockeyTeam.coaches");
            query2.setHint(QueryHints.BATCH_TYPE, BatchFetchType.EXISTS);
            hockeyPlayerResults = query2.getResultList();
            for (HockeyPlayer player : hockeyPlayerResults) {
                player.getVitals().getTeamVitals().getHockeyTeam().getCoaches();
            }

            em.clear();
            m_session.getIdentityMapAccessor().initializeAllIdentityMaps();
        } finally  {
            try {
                closeEntityManagerAndTransaction(em);
            } catch (RuntimeException re){
                //ignore
            }
            if (counter != null) {
                counter.remove();
            }
        }
    }

    public void testAggregateFieldAttributeOverrides() {
        clearCache();
        DatabaseSessionImpl m_session = getDatabaseSession();

        EntityManager em = createEntityManager();

        beginTransaction(em);

        Place place = new Place();
        place.setCountryCode("US");
        place.setName("Nowhere");
        place.setAddress1(new Location("12 Main Street", "US"));
        place.setAddress2(new Location("34 Easy Street", "US"));

        em.persist(place);

        em.flush();

        rollbackTransaction(em);
    }
    
    /**
     * Bug 474956 RepeatableUnitOfWork linked by Embeddable in shared cache in specific scenario
     * Test the invalidation and refresh of a parent object with an Embeddable instantiated
     * with non-null values. After associating the parent with another object, the Embeddable
     * should not reference a UnitOfWork (via a change listener) within the shared cache.
     */
    public void testInvalidateAndRefreshEmbeddableParent() {
        // test depends on weaving
        if (!isWeavingEnabled()) {
            warning("Test depends on weaving and change tracking");
            return;
        }
        
        HockeyPuck puck = null;
        HockeyRink rink = null;
        
        // setup
        clearCache();
        
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);
            
            puck = new HockeyPuck();
            puck.setId(1);
            puck.setName("MrWraparound");
            puck.getSponsor().setName("ACME Cloud Computing Company, Inc.");
            puck.getSponsor().setSponsorshipValue(1000000);
            em.persist(puck);
            
            commitTransaction(em);
        } finally {
            closeEntityManager(em);
        }
        
        // test
        em = createEntityManager();
        try {
            // 1. invalidate existing Entity in the cache
            JpaHelper.getDatabaseSession(getEntityManagerFactory()).getIdentityMapAccessor().invalidateObject(puck.getId(), HockeyPuck.class);
            assertFalse("Existing cached HockeyPuck should not be valid", 
                JpaHelper.getDatabaseSession(getEntityManagerFactory()).getIdentityMapAccessor().isValid(puck.getId(), HockeyPuck.class));
            
            beginTransaction(em);
            
            // 2. create new Entity and persist
            rink = new HockeyRink();
            rink.setId(1);
            em.persist(rink);
            
            // 3. load existing Entity
            puck = em.createQuery("select object(p) from HockeyPuck p where p.id = " + puck.getId(), HockeyPuck.class).getSingleResult();
            assertNotNull("HockeyPuck loaded should not be null", puck);
            
            // 4. associate loaded existing Entity with persisted new Entity
            rink.setPuck(puck);
            
            commitTransaction(em);
        } finally {
            closeEntityManager(em);
        }
        
        // verify, go directly to the shared cache for the parent Entity
        try {
            HockeyPuck cachedPuck = (HockeyPuck)JpaHelper.getDatabaseSession(getEntityManagerFactory()).getIdentityMapAccessor().getFromIdentityMap(puck.getId(), HockeyPuck.class);
            HockeySponsor sponsor = cachedPuck.getSponsor();
            if (sponsor instanceof ChangeTracker) {
                PropertyChangeListener listener = ((ChangeTracker)sponsor)._persistence_getPropertyChangeListener();
                // listener can be null
                if (listener != null && listener instanceof AttributeChangeListener) {
                    assertNull("UnitOfWork referenced in Embeddable referenced by an object in the shared cache", ((AttributeChangeListener)listener).getUnitOfWork());
                }
            } else {
                fail("Config error: HockeyPuck/HockeySponsor is not change tracked");
            }
        } finally {
            // reset
            em = createEntityManager();
            beginTransaction(em);
            
            if (puck != null) {
                puck = em.find(HockeyPuck.class, puck.getId());
                if (puck != null) {
                    em.remove(puck);
                }
            }
            if (rink != null) {
                rink = em.find(HockeyRink.class, rink.getId());
                if (rink != null) {
                    em.remove(rink);
                }
            }
            
            commitTransaction(em);
            closeEntityManager(em);
        }
    }

}
