/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation..  All rights reserved.
*/

package mondrian.xmla.impl;

import mondrian.olap.*;
import mondrian.server.DynamicContentFinder;
import mondrian.util.Pair;
import mondrian.xmla.DataSourcesConfig;

import junit.framework.TestCase;

import org.eigenbase.xom.*;
import org.eigenbase.xom.Parser;

import org.olap4j.impl.Olap4jUtil;

import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Unit test for DynamicDatasourceXmlaServlet
 *
 * @author Thiyagu, Ajit
 * @since Mar 30, 2007
 */
public class DynamicDatasourceXmlaServletTest extends TestCase {

    private static final String CATALOG_0_NAME = "FoodMart0";
    private static final String CATALOG_1_NAME = "FoodMart1";
    private static final String CATALOG_2_NAME = "FoodMart2";
    private static final String CATALOG_0_DEFINITION =
        "<Catalog name='" + CATALOG_0_NAME + "'>"
        + "<Definition>Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart0;"
        + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver"
        + "</Definition>"
        + "</Catalog>";
    private static final String CATALOG_0_UPDATED_DEFINITION =
        "<Catalog name='" + CATALOG_0_NAME + "'>"
        + "<Definition>Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart0.0;"
        + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver"
        + "</Definition>"
        + "</Catalog>";
    private static final String CATALOG_1_DEFINITION =
        "<Catalog name='" + CATALOG_1_NAME + "'>"
        + "<Definition>Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart1;"
        + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver"
        + "</Definition>"
        + "</Catalog>";
    private static final String CATALOG_2_DEFINITION =
        "<Catalog name='" + CATALOG_2_NAME + "'>"
        + "<Definition>Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart2;"
        + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver"
        + "</Definition>"
        + "</Catalog>";
    private static final String DATASOURCE_1_NAME = "DATASOURCENAME1";
    private static final String DATASOURCE_2_NAME = "DATASOURCENAME2";

    public void testFlushObsoleteCatalogsForNewCatalog() throws Exception {
        DataSourcesConfig.DataSources newDataSources =
            getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION);
        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                "inline:" + getDataSourceContent(CATALOG_0_DEFINITION));
        finder.flushObsoleteCatalogs(newDataSources);
        assertTrue(finder.flushCatalogList().isEmpty());
        finder.shutdown();
    }

    public void testFlushObsoleteCatalogsForUpdateCatalog() throws Exception {
        DataSourcesConfig.DataSources newDataSources =
            getDataSources(CATALOG_0_UPDATED_DEFINITION);
        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                "inline:" + getDataSourceContent(CATALOG_0_DEFINITION));
        finder.flushObsoleteCatalogs(newDataSources);
        assertTrue(finder.flushCatalogList().contains(CATALOG_0_NAME));
        finder.shutdown();
    }

    public void testFlushObsoleteCatalogsForUnchangedCatalog() throws Exception
    {
        DataSourcesConfig.DataSources newDataSources =
            getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION);
        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                "inline:" + getDataSourceContent(CATALOG_0_DEFINITION));
        finder.flushObsoleteCatalogs(newDataSources);
        assertFalse(finder.flushCatalogList().contains(CATALOG_0_NAME));
        finder.shutdown();
    }

    public void testFlushObsoleteCatalogsForDeletedCatalog() throws Exception {
        DataSourcesConfig.DataSources newDataSources =
            getDataSources(CATALOG_1_DEFINITION);
        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                "inline:" + getDataSourceContent(CATALOG_0_DEFINITION));
        finder.flushObsoleteCatalogs(newDataSources);
        assertTrue(finder.flushCatalogList().contains(CATALOG_0_NAME));
        finder.shutdown();
    }

    public void testMergeDataSourcesForAlteringCatalogAcrossDataSources()
        throws Exception
    {
        DataSourcesConfig.DataSources newDataSources =
            getDataSources(
                Olap4jUtil.mapOf(
                    DATASOURCE_1_NAME,
                    new String[] {
                        CATALOG_0_UPDATED_DEFINITION,
                        CATALOG_1_DEFINITION},
                    DATASOURCE_2_NAME,
                    new String[] {
                        CATALOG_2_DEFINITION}));
        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                "inline:" + getDataSourceContent(CATALOG_0_DEFINITION));
        finder.flushObsoleteCatalogs(newDataSources);
        assertTrue(finder.flushCatalogList().contains(CATALOG_0_NAME));
        finder.shutdown();
    }

    public void testAreCatalogsEqual() throws Exception {
        DataSourcesConfig.DataSources newDataSources =
            getDataSources(
                CATALOG_0_DEFINITION,
                CATALOG_0_UPDATED_DEFINITION,
                CATALOG_1_DEFINITION,
                CATALOG_2_DEFINITION);
        DataSourcesConfig.DataSource datasource = newDataSources.dataSources[0];
        DataSourcesConfig.Catalog catalog0 = datasource.catalogs.catalogs[0];
        DataSourcesConfig.Catalog catalog0Updated =
            datasource.catalogs.catalogs[1];
        DataSourcesConfig.Catalog catalog1 = datasource.catalogs.catalogs[2];
        DataSourcesConfig.Catalog catalog2 = datasource.catalogs.catalogs[3];
        assertFalse(
            DynamicContentFinder.areCatalogsEqual(
                datasource, catalog0, datasource, catalog0Updated));
        assertTrue(
            DynamicContentFinder.areCatalogsEqual(
                datasource, catalog0, datasource, catalog0));
        assertFalse(
            DynamicContentFinder.areCatalogsEqual(
                datasource, catalog1, datasource, catalog2));
    }

    private static DataSourcesConfig.DataSources getDataSources(
        String... catalogs)
        throws XOMException
    {
        return getDataSources(Olap4jUtil.mapOf(DATASOURCE_1_NAME, catalogs));
    }

    private static String getDataSourceContent(String... catalogs) {
        return getDataSourceString(
            Olap4jUtil.mapOf(DATASOURCE_1_NAME, catalogs));
    }

    private static DataSourcesConfig.DataSources getDataSources(
        Map<String, String[]> dsCatalog)
        throws XOMException
    {
        final String str = getDataSourceString(dsCatalog);
        final Parser xmlParser = XOMUtil.createDefaultParser();
        final DOMWrapper def = xmlParser.parse(str);
        return new DataSourcesConfig.DataSources(def);
    }

    private static String getDataSourceString(Map<String, String[]> dsCatalog) {
        StringBuilder ds = new StringBuilder();
        ds.append("<?xml version=\"1.0\"?>");
        ds.append("<DataSources>");
        for (Map.Entry<String, String[]> entry : dsCatalog.entrySet()) {
            final String dsName = entry.getKey();
            ds.append("<DataSource> ");
            ds.append("  <DataSourceName>")
                .append(dsName)
                .append("</DataSourceName>");
            ds.append(
                "       <DataSourceDescription>"
                + "DATASOURCE_DESCRIPTION"
                + "</DataSourceDescription>");
            ds.append("       <URL>http://localhost:8080/mondrian/xmla</URL>");
            ds.append("       <DataSourceInfo>Provider=mondrian;")
                .append("Jdbc=jdbc:oracle:thin:foodmart/foodmart@")
                .append("//marmalade.hydromatic.net:1521/XE;")
                .append("JdbcUser=foodmart;JdbcPassword=foodmart;")
                .append("JdbcDrivers=oracle.jdbc.OracleDriver;")
                .append("Catalog=/WEB-INF/queries/FoodMart.xml")
                .append("</DataSourceInfo>");
            ds.append("       <ProviderName>Mondrian</ProviderName>");
            ds.append("       <ProviderType>MDP</ProviderType>");
            ds.append("       <AuthenticationMode>")
                .append("Unauthenticated")
                .append("</AuthenticationMode>");
            ds.append("       <Catalogs>");
            final String[] catalogs = entry.getValue();
            for (String catalog : catalogs) {
                ds.append(catalog);
            }
            ds.append("       </Catalogs>");
            ds.append("</DataSource>");
        }
        ds.append("</DataSources>");
        return ds.toString();
    }

    public void testReloadDataSources() throws Exception {
        DataSourcesConfig.DataSources ds1 =
            getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION);
        DataSourcesConfig.DataSources ds2 =
            getDataSources(CATALOG_1_DEFINITION, CATALOG_2_DEFINITION);

        File dsFile = File.createTempFile(
            getClass().getName()  + "-datasources", ".xml");
        dsFile.deleteOnExit();

        OutputStream out = new FileOutputStream(dsFile);
        out.write(ds1.toXML().getBytes());
        out.close();

        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                dsFile.toURL().toString());

        out = new FileOutputStream(dsFile);
        out.write(ds2.toXML().getBytes());
        out.close();

        finder.reloadDataSources();
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME));
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME));
        assertFalse(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME));

        out = new FileOutputStream(dsFile);
        out.write(ds1.toXML().getBytes());
        out.flush();

        finder.reloadDataSources();
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME));
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME));
        assertFalse(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME));
        finder.shutdown();
    }

    public void testAutoReloadDataSources() throws Exception {
        DataSourcesConfig.DataSources ds1 =
            getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION);
        DataSourcesConfig.DataSources ds2 =
            getDataSources(CATALOG_1_DEFINITION, CATALOG_2_DEFINITION);

        File dsFile = File.createTempFile(
            getClass().getName()  + "-datasources", ".xml");
        dsFile.deleteOnExit();

        OutputStream out = new FileOutputStream(dsFile);
        out.write(ds1.toXML().getBytes());
        out.flush();

        final MockDynamicContentFinder finder =
            new MockDynamicContentFinder(
                dsFile.toURL().toString());

        out = new FileOutputStream(dsFile);
        out.write(ds2.toXML().getBytes());
        out.close();

        finder.reloadDataSources();
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME));
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME));
        assertFalse(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME));

        out = new FileOutputStream(dsFile);
        out.write(ds1.toXML().getBytes());
        out.close();

        // Wait for it to auto-reload.
        final Pair<Long, TimeUnit> interval =
            Util.parseInterval(
                String.valueOf(
                    MondrianProperties.instance()
                        .XmlaSchemaRefreshInterval.get()),
                TimeUnit.MILLISECONDS);
        Thread.sleep(
            interval.right.toMillis(interval.left)
            + 1000);

        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME));
        assertTrue(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME));
        assertFalse(
            finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME));
        finder.shutdown();
    }

    private static class MockDynamicContentFinder extends DynamicContentFinder
    {
        private List<String> flushCatalogList = new ArrayList<String>();

        public MockDynamicContentFinder(String dataSources)
        {
            super(dataSources);
        }

        protected void flushCatalog(String catalogName) {
            flushCatalogList.add(catalogName);
        }

        public List<String> flushCatalogList() {
            return flushCatalogList;
        }

        public boolean containsCatalog(
            String datasourceName,
            String catalogName)
        {
            return locateCatalog(datasourceName, catalogName) != null;
        }

        public synchronized DataSourcesConfig.Catalog locateCatalog(
            String datasourceName,
            String catalogName)
        {
            for (DataSourcesConfig.DataSource ds : dataSources.dataSources) {
                if (ds.name.equals(datasourceName)) {
                    for (DataSourcesConfig.Catalog catalog
                             : ds.catalogs.catalogs)
                    {
                        if (catalog.name.equals(catalogName)) {
                            return catalog;
                        }
                    }
                }
            }
            return null;
        }
    }
}

// End DynamicDatasourceXmlaServletTest.java
