/*
 * TestFetchGroups.java
 *
 * Created on October 10, 2006, 5:46 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.persistence.kernel;

import java.util.Arrays;
import java.util.BitSet;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.persistence.FetchPlan;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.kernel.common.apps.AttachA;
import org.apache.openjpa.persistence.kernel.common.apps.AttachB;
import org.apache.openjpa.persistence.kernel.common.apps.AttachC;
import org.apache.openjpa.persistence.kernel.common.apps.AttachD;
import org.apache.openjpa.persistence.kernel.common.apps.AttachE;
import org.apache.openjpa.persistence.kernel.common.apps.AttachF;
import org.apache.openjpa.persistence.kernel.common.apps.FetchGroupTestObject;
import org.apache.openjpa.persistence.kernel.common.apps.
        FetchGroupTestObjectChild;

public class TestFetchGroups extends BaseKernelTest {

    private int oid1;
    private int oid2;
    private int oidc1;

    public TestFetchGroups(String s) {
        super(s);
    }

    /**
     * Creates a new instance of TestFetchGroups
     */
    public TestFetchGroups() {
    }

    @Override
    public void setUp() throws Exception {
        super.setUp(FetchGroupTestObject.class, FetchGroupTestObjectChild.class,
            AttachA.class, AttachB.class, AttachC.class, AttachD.class, AttachE.class, AttachF.class);

        FetchGroupTestObject o1 = new FetchGroupTestObject();
        // the value that 'a' is set to is important -- TestFetchGroupsExtent
        // and TestFetchGroupsQuery rely on this
        o1.setA(5);
        o1.setB("foo");
        //o1.setC (new BigInteger (89));
        o1.setD(new Date());
        o1.setE("e-foo");
        o1.setF("f-foo");

        FetchGroupTestObject o2 = new FetchGroupTestObject();
        // the value that 'a' is set to is important -- TestFetchGroupsExtent
        // and TestFetchGroupsQuery rely on this
        o2.setA(3);
        o2.setB("bar");
        //o2.setC (new BigInteger (13));
        o2.setD(new Date());
        o2.setE("e-bar");
        o2.setF("f-bar");
        o2.setG(o1);
        o2.setH(o1);

        FetchGroupTestObjectChild c1 = new FetchGroupTestObjectChild();
        // the value that 'a' is set to is important -- TestFetchGroupsExtent
        // and TestFetchGroupsQuery rely on this
        c1.setA(4);
        c1.setB("child");
        c1.setD(new Date());
        c1.setE("e-baz");
        c1.setF("f-baz");
        c1.setG(o1);
        c1.setH(o1);
        c1.setChildA(1);
        c1.setChildB(2);
        c1.setChildC(3);
        c1.setChildD(4);

        OpenJPAEntityManager pm = getPM();
        startTx(pm);

        pm.persist(o1);
        pm.persist(o2);
        pm.persist(c1);
        endTx(pm);

        oid1 = o1.getId();
        oid2 = o2.getId();
        oidc1 = c1.getId();

        endEm(pm);
    }

    public void testFetchGroupsFromConfiguration() {
        Map props = new HashMap();
        props.put("openjpa.FetchGroups", "default,fg1,fg2");
        OpenJPAEntityManagerFactory factory = getEmf(props);

        OpenJPAEntityManager pm = factory.createEntityManager();
        checkGroups(pm, new String[]{ "fg1", "fg2" });
        factory.close();
    }

    public void testFetchGroupsNoConfiguration() {
        OpenJPAEntityManager pm = getPM();

        FetchGroupTestObject o1 = getO1(pm);
        FetchGroupTestObject o2 = getO2(pm);

        // only field a should be loaded.
        checkObject(pm, o1, true, false, false, false, false, false);

        // upon loading field b, fields c and d should also be loaded,
        // but e and f should not.
        o1.getB();
        checkObject(pm, o1, true, true, true, true, false, false);

        // loading field h should not cause any of the others to be loaded.
        assertEquals(o1, o2.getH());
        checkObject(pm, o2, true, false, false, false, false, false);

        // loading field g should cause e and f to be loaded.
        assertEquals(o1, o2.getG());
        checkObject(pm, o2, true, false, false, false, true, true);
    }

    public void testRetrieveAll() {
        OpenJPAEntityManager pm = getPM();

        FetchGroupTestObject o1 = getO1(pm);
        FetchGroupTestObject o2 = getO2(pm);

        // only field a should be loaded.
        checkObject(pm, o1, true, false, false, false, false, false);
        checkObject(pm, o2, true, false, false, false, false, false);

        // only field a should be loaded.
        pm.retrieve(o1);
        checkObject(pm, o1, true, false, false, false, false, false);

        // Add groups 1 and 3 to the default fetch configuration.
        pm.getFetchPlan().addFetchGroup("g1");
        pm.getFetchPlan().addFetchGroup("g3");

        // Retrieve o1's "DFG" which will actually do all configured
        // fetch groups.
        // DFG fields and fields in groups 1 and 3 should be loaded
        pm.retrieve(o1);
        checkObject(pm, o1, true, true, true, true, false, false);
    }

    public void testFetchGroupConfiguration() {
        OpenJPAEntityManager pm = getPM();
        FetchPlan fetch = pm.getFetchPlan();

        checkGroups(pm, new String[0]);

        fetch.addFetchGroup("foo");
        checkGroups(pm, new String[]{ "foo" });

        fetch.addFetchGroup("bar");
        fetch.addFetchGroup("baz");
        checkGroups(pm, new String[]{ "foo", "bar", "baz" });

        fetch.addFetchGroup("a");
        fetch.addFetchGroup("b");
        fetch.addFetchGroup("c");
        fetch.addFetchGroup("d");
        checkGroups(pm, new String[]
            { "foo", "bar", "baz", "a", "b", "c", "d" });

        fetch.removeFetchGroup("bar");
        checkGroups(pm, new String[]{ "foo", "baz", "a", "b", "c", "d" });

        fetch.removeFetchGroup("baz");
        fetch.removeFetchGroup("c");
        checkGroups(pm, new String[]{ "foo", "a", "b", "d" });

        fetch.clearFetchGroups().addFetchGroup(FetchPlan.GROUP_DEFAULT);
        checkGroups(pm, new String[0]);
    }

    private void checkGroups(OpenJPAEntityManager pm, String[] groups) {
        HashSet groupSet = new HashSet(Arrays.asList(groups));
        groupSet.add(FetchPlan.GROUP_DEFAULT);
        assertEquals("groupSet dont match", groupSet,
            new HashSet(pm.getFetchPlan().getFetchGroups()));
    }

    public void testFetchGroupsChildWithConfiguration() {
        OpenJPAEntityManager pm = getPM();
        pm.getFetchPlan().addFetchGroup("g1");
        pm.getFetchPlan().addFetchGroup("g3");

        // get this so that h's value is loaded into cache.
        FetchGroupTestObject o1 = getO1(pm);

        FetchGroupTestObjectChild c1 = getC1(pm);

        // DFG fields and fields in groups 1 and 3 should be loaded
        checkChildObject(pm, c1, true, true, true, true, false, false,
            true, true, false, true);

        // upon accessing field b, nothing should change.
        c1.getB();
        checkChildObject(pm, c1, true, true, true, true, false, false,
            true, true, false, true);

        c1.getH();
    }

    public void testFetchGroupsWithConfiguration() {
        OpenJPAEntityManager pm = getPM();

        pm.getFetchPlan().addFetchGroup("g1");
        pm.getFetchPlan().addFetchGroup("g3");

        FetchGroupTestObject o1 = getO1(pm);
        FetchGroupTestObject o2 = getO2(pm);

        // DFG fields and fields in groups 1 and 3 should be loaded
        checkObject(pm, o1, true, true, true, true, false, false);

        // upon accessing field b, nothing should change.
        o1.getB();
        checkObject(pm, o1, true, true, true, true, false, false);

        // loading field h should not cause any of the others to be loaded.
        assertEquals(o1, o2.getH());
        checkObject(pm, o2, true, true, true, true, false, false);

        // loading field g should cause e and f to be loaded.
        assertEquals(o1, o2.getG());
        checkObject(pm, o2, true, true, true, true, true, true);
    }

    /**
     * Tests that relation fields are loaded immediately when
     * they are in one of the PM's configured fetch groups.
     */
    public void testRelationsLoaded() {
        OpenJPAEntityManager pm = getPM();
        pm.getFetchPlan().addFetchGroup("g2");

        // note: important the o1 is *not* in pm's cache at this point, so that
        // we know it takes another datastore trip to get o1

        // load o2 and retrieve its state manager
        OpenJPAStateManager sm = getStateManager(getO2(pm), pm);
        assertNotNull("SM is NULL", sm);

        // 'g' is the name of a 1-1 relation field to o1 in configured
        // fetch group 'g2'; make sure it is loaded
        int field = sm.getMetaData().getField("g").getIndex();
        try {
            assertTrue(sm.getLoaded().get(field));
            assertEquals(oid1,
                ((FetchGroupTestObject) sm.fetchObjectField(field)).getId());
        } catch (junit.framework.AssertionFailedError afe) {
            bug(623, afe, "One to one mappings do not work with custom "
                + "fetch groups");
        }
    }

    protected void checkObject(OpenJPAEntityManager pm,
        FetchGroupTestObject o, boolean a, boolean b,
        boolean c, boolean d, boolean e, boolean f) {
        OpenJPAStateManager sm = getStateManager(o, pm);
        BitSet loaded = sm.getLoaded();

        FieldMetaData[] fmds = sm.getMetaData().getFields();
        int i = 0;
        for (; i < fmds.length; i++) {
            if (fmds[i].getName().equals("a"))
                assertEquals(a, loaded.get(i));
            else if (fmds[i].getName().equals("b"))
                assertEquals(b, loaded.get(i));
            else if (fmds[i].getName().equals("c"))
                assertEquals(c, loaded.get(i));
            else if (fmds[i].getName().equals("d"))
                assertEquals(d, loaded.get(i));
            else if (fmds[i].getName().equals("e"))
                assertEquals(e, loaded.get(i));
            else if (fmds[i].getName().equals("f"))
                assertEquals(f, loaded.get(i));
        }
    }

    protected void checkChildObject(OpenJPAEntityManager pm,
        FetchGroupTestObjectChild o, boolean a, boolean b,
        boolean c, boolean d, boolean e, boolean f, boolean childA,
        boolean childB, boolean childC, boolean childD) {
        checkObject(pm, o, a, b, c, d, e, f);

        OpenJPAStateManager sm = getStateManager(o, pm);
        BitSet loaded = sm.getLoaded();

        FieldMetaData[] fmds = sm.getMetaData().getFields();
        int i = 0;
        for (; i < fmds.length; i++) {
            if (fmds[i].getName().equals("childA"))
                assertEquals(childA, loaded.get(i));
            else if (fmds[i].getName().equals("childB"))
                assertEquals(childB, loaded.get(i));
            else if (fmds[i].getName().equals("childC"))
                assertEquals(childC, loaded.get(i));
            else if (fmds[i].getName().equals("childD"))
                assertEquals(childD, loaded.get(i));
        }
    }

    protected FetchGroupTestObject getO1(OpenJPAEntityManager pm) {
        return pm.find(FetchGroupTestObject.class, oid1);
    }

    protected FetchGroupTestObject getO2(OpenJPAEntityManager pm) {
        return pm.find(FetchGroupTestObject.class, oid2);
    }

    protected FetchGroupTestObjectChild getC1(OpenJPAEntityManager pm) {
        return pm.find(FetchGroupTestObjectChild.class, oidc1);
    }

    /**
     * Tests that named fetch groups actually bring in the
     * managed object.
     */
    public void testFetchGroupInstantiated() {
        OpenJPAEntityManager pm = getPM();
        startTx(pm);
        AttachE e = new AttachE();
        AttachB b = new AttachB();
        e.setB(b);
        pm.persist(e);

        endTx(pm);
        endEm(pm);

        pm = getPM();
        startTx(pm);
        assertSize(0, pm.getManagedObjects());
        pm.createExtent(AttachE.class, true).iterator().next();
        // make sure relation is not loaded
        assertSize(1, pm.getManagedObjects());
        rollbackTx(pm);
        endEm(pm);

        pm = getPM();
        startTx(pm);
        // now make sure we load relations
        pm.getFetchPlan().addFetchGroup("all");
        assertSize(0, pm.getManagedObjects());
        pm.createExtent(AttachE.class, true).iterator().next();
        // make sure relation is loaded
        assertSize(2, pm.getManagedObjects());
        rollbackTx(pm);
        endEm(pm);
    }
}
