/*
 * 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.jdbc.order;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.meta.MetaDataSerializer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.ArgumentException;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.jdbc.XMLPersistenceMappingParser;
import org.apache.openjpa.persistence.jdbc.XMLPersistenceMappingSerializer;
import org.apache.openjpa.persistence.test.AbstractCachedEMFTestCase;

public class TestOrderColumnXML extends AbstractCachedEMFTestCase {

    /*
     * Validates the use of the nullable attribute on OrderColumn through
     * an entity defined in orm.xml
     */
    public void testOrderColumnNullableFalse() {

        OpenJPAEntityManagerFactorySPI emf1 =
            (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence.
            createEntityManagerFactory("BaseNoNullTest",
            "org/apache/openjpa/persistence/jdbc/order/" +
            "order-persistence-4.xml");

        OpenJPAConfiguration conf = emf1.getConfiguration();
        MetaDataRepository repos = conf.getMetaDataRepositoryInstance();

        // Force entity resolution
        repos.getMetaData(BaseTestEntity2.class, null, true);

        OpenJPAEntityManagerSPI em = emf1.createEntityManager();

        validateOrderColumnNullable(emf1, BaseTestEntity2.class,
            "one2Melems", false);

        validateOrderColumnNullable(emf1, BaseTestEntity2.class,
                "collelems", false);

        validateOrderColumnNullable(emf1, BaseTestEntity2.class,
                "m2melems", false);

        em.close();
        try {
            if (emf1 != null)
                cleanupEMF(emf1);
        } catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage());
        }
    }

    /*
     * Validates the use of the columnDefinition attribute on OrderColumn.
     */
    public void testOrderColumnColumnDefinition() {

        OpenJPAEntityManagerFactorySPI emf1 =
            (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence.
            createEntityManagerFactory("ColDefTest",
            "org/apache/openjpa/persistence/jdbc/order/" +
            "order-persistence-2.xml");

        // Create the EM.  This will spark the mapping tool.
        OpenJPAEntityManagerSPI em = emf1.createEntityManager();
        //
        // Create a collection using a custom column definition
        validateOrderColumnDef(emf1, ColDefTestEntity.class,
            "one2Mcoldef", "INTEGER");

        validateOrderColumnDef(emf1, ColDefTestEntity.class,
            "collcoldef", "INTEGER");

        validateOrderColumnDef(emf1, ColDefTestEntity.class,
            "m2mcoldef", "INTEGER");

        // Add and query some values
        ColDefTestEntity cdent = new ColDefTestEntity();

        ColDefTestElement cdel1 = new ColDefTestElement("Element1");
        ColDefTestElement cdel2 = new ColDefTestElement("Element2");
        ColDefTestElement cdel3 = new ColDefTestElement("Element3");

        List<ColDefTestElement> one2Mcoldef =
            new ArrayList<>();
        one2Mcoldef.add(cdel3);
        one2Mcoldef.add(cdel2);
        one2Mcoldef.add(cdel1);
        cdent.setOne2Mcoldef(one2Mcoldef);

        Set<ColDefTestElement> collcoldef =
            new LinkedHashSet<>();
        collcoldef.add(cdel1);
        collcoldef.add(cdel2);
        collcoldef.add(cdel3);
        cdent.setCollcoldef(collcoldef);

        List<ColDefTestElement> m2mcoldef = new ArrayList<>();
        m2mcoldef.add(cdel2);
        m2mcoldef.add(cdel1);
        m2mcoldef.add(cdel3);
        cdent.setM2mcoldef(m2mcoldef);

        em.getTransaction().begin();
        em.persist(cdent);
        em.getTransaction().commit();

        em.close();
        try {
            if (emf1 != null)
                cleanupEMF(emf1);
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }

    /**
     * Validates the use of the table attribute defined in XML
     */
    public void testOrderColumnTableXML() {

        OpenJPAEntityManagerFactorySPI emf1 =
            (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence.
            createEntityManagerFactory("TableTest",
            "org/apache/openjpa/persistence/jdbc/order/" +
            "order-persistence-5.xml");

        OpenJPAEntityManagerSPI em = emf1.createEntityManager();

        validateOrderColumnTable(emf1, BaseTestEntity3.class, "one2Melems",
            "xml_o2m_table", "one2MOrder");

        validateOrderColumnTable(emf1, BaseTestEntity3.class, "m2melems",
             "xml_m2m_table", "m2morder");

        validateOrderColumnTable(emf1, BaseTestEntity3.class, "collelems",
             "xml_coll_table", "collelems_ORDER");

        em.close();
        try {
            if (emf1 != null)
                cleanupEMF(emf1);
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }


    /*
     * Validates OrderBy and OrderColumn should not be specified together per
     * the JPA 2.0 spec.
     */
    public void testOrderColumnOrderBy() {

        OpenJPAEntityManagerFactorySPI emf1 = null;
        OpenJPAEntityManagerSPI em = null;
        try {
            emf1 = (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence.
                createEntityManagerFactory("ObOcTest",
                    "org/apache/openjpa/persistence/jdbc/order/" +
                    "order-persistence-3.xml");

            em = emf1.createEntityManager();

            ObOcEntity ent = new ObOcEntity();
            List<Integer> intList = new ArrayList<>();
            intList.add(10);
            intList.add(20);
            ent.setIntList(intList);

            em.getTransaction().begin();
            em.persist(intList);
            em.getTransaction().commit();

            em.close();
            em = null;
            fail("An exception should have been thrown.");
        } catch (Exception e) {
            assertException(e, ArgumentException.class);
        } finally {
            if (em != null)
                em.close();
        }
        try {
            if (emf1 != null)
                cleanupEMF(emf1);
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }

    public void testOrderColumnMetaDataSerialization()
        throws Exception {

        OpenJPAEntityManagerFactorySPI emf1 =
            (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence.
            createEntityManagerFactory("BaseTest",
            "org/apache/openjpa/persistence/jdbc/order/" +
            "order-persistence.xml");

        OpenJPAConfiguration conf = emf1.getConfiguration();
        MetaDataRepository repos = conf.newMetaDataRepositoryInstance();

        // Force entity resolution
        repos.getMetaData(BaseTestEntity1.class, null, true);

        XMLPersistenceMappingSerializer ser =
            new XMLPersistenceMappingSerializer((JDBCConfiguration)conf);
        ser.addAll(repos);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ser.serialize(new OutputStreamWriter(out), MetaDataSerializer.PRETTY);
        byte[] bytes = out.toByteArray();

        XMLPersistenceMappingParser parser =
            new XMLPersistenceMappingParser((JDBCConfiguration)conf);
        parser.parse(new InputStreamReader
            (new ByteArrayInputStream(bytes)), "bytes");
        MetaDataRepository mdr2 = parser.getRepository();

        ClassMetaData _entityMeta2 =
            mdr2.getMetaData(BaseTestEntity1.class, null, true);

        // Assert metadata is populated correctly
        FieldMapping fm = (FieldMapping)_entityMeta2.getField("one2Melems");
        Column oc = fm.getOrderColumn();
        assertNotNull(oc);
        assertEquals(oc.getName(),"one2MOrder");

        fm = (FieldMapping)_entityMeta2.getField("m2melems");
        oc = fm.getOrderColumn();
        assertNotNull(oc);
        assertEquals(oc.getName(),"m2morder");

        fm = (FieldMapping)_entityMeta2.getField("collelems");
        oc = fm.getOrderColumn();
        assertNotNull(oc);
        assertEquals(oc.getName(),"collelems_ORDER");

        try {
            if (emf1 != null)
                cleanupEMF(emf1);
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }

    private Column getOrderColumn(OpenJPAEntityManagerFactorySPI emf1,
        Class clazz, String fieldName) {
        JDBCConfiguration conf = (JDBCConfiguration) emf1.getConfiguration();
        ClassMapping cls = conf.getMappingRepositoryInstance().
            getMapping(clazz, null, true);
        FieldMapping fm = cls.getFieldMapping(fieldName);
        Column oc = fm.getOrderColumn();
        assertNotNull(oc);
        return oc;
    }

    private void validateOrderColumnTable(
            OpenJPAEntityManagerFactorySPI emf1,
            Class clazz, String fieldName, String tableName,
            String columnName) {
            Column oc = getOrderColumn(emf1, clazz, fieldName);
            // Verify the oc has the correct table name
            assertTrue(oc.getTableName().equalsIgnoreCase(tableName));
            // Verify the table exists in the db
            assertTrue(tableAndColumnExists(emf1, null, tableName, null,
                columnName));
    }

    private void validateOrderColumnDef(
            OpenJPAEntityManagerFactorySPI emf1, Class clazz, String fieldName,
            String type) {
            Column oc = getOrderColumn(emf1, clazz, fieldName);
            assertEquals(type, oc.getTypeName());
    }

    private void validateOrderColumnNullable(
            OpenJPAEntityManagerFactorySPI emf1, Class clazz, String fieldName,
            boolean nullable) {
            Column oc = getOrderColumn(emf1, clazz, fieldName);
            assertEquals(nullable, !oc.isNotNull());
    }


    /**
     * Method to verify a table was created for the given name and schema
     */
    private boolean tableAndColumnExists(OpenJPAEntityManagerFactorySPI emf1,
            OpenJPAEntityManagerSPI em, String tableName, String schemaName,
            String columnName) {
        JDBCConfiguration conf = (JDBCConfiguration) emf1.getConfiguration();
        DBDictionary dict = conf.getDBDictionaryInstance();
        OpenJPAEntityManagerSPI em1 = em;

        // If no em supplied, create one
        if (em1 == null) {
            em1 = emf1.createEntityManager();
        }
        Connection conn = (Connection)em1.getConnection();
        try {
            DatabaseMetaData dbmd = conn.getMetaData();
            // (meta, catalog, schemaName, tableName, conn)
            Column[] cols = dict.getColumns(dbmd, null, null,
                    tableName, columnName, conn);
            if (cols != null && cols.length == 1) {
                Column col = cols[0];
                String colName = col.getName();
                if (col.getTableName().equalsIgnoreCase(tableName) &&
                    (schemaName == null ||
                    col.getSchemaName().equalsIgnoreCase(schemaName)) &&
                    colName.equalsIgnoreCase(columnName))
                    return true;
            }
        } catch (Throwable e) {
            fail("Unable to get column information.");
        } finally {
            if (em == null) {
                em1.close();
            }
        }
        return false;
    }
    /**
     * Closes a specific entity manager factory and cleans up
     * associated tables.
     */
    private void cleanupEMF(OpenJPAEntityManagerFactorySPI emf1)
      throws Exception {

        if (emf1 == null)
            return;

        try {
            clear(emf1);
        } catch (Exception e) {
            // if a test failed, swallow any exceptions that happen
            // during tear-down, as these just mask the original problem.
            if (testResult.wasSuccessful())
                throw e;
        } finally {
            closeEMF(emf1);
        }
    }
 }
