/*
// 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-2014 Pentaho and others
// All Rights Reserved.
*/
package mondrian.xmla.test;

import mondrian.olap.*;
import mondrian.olap.Util.PropertyList;
import mondrian.rolap.RolapConnectionProperties;
import mondrian.server.StringRepositoryContentFinder;
import mondrian.test.DiffRepository;
import mondrian.test.TestContext;
import mondrian.xmla.*;
import mondrian.xmla.impl.DefaultXmlaRequest;
import mondrian.xmla.impl.DefaultXmlaResponse;

import junit.framework.*;

import org.apache.log4j.Logger;

import org.custommonkey.xmlunit.XMLAssert;
import org.custommonkey.xmlunit.XMLUnit;
import org.w3c.dom.*;

import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Unit test for refined Mondrian's XML for Analysis API (package
 * {@link mondrian.xmla}).
 *
 * @author Gang Chen
 */
public class XmlaTest extends TestCase {

    private static final Logger LOGGER =
            Logger.getLogger(XmlaTest.class);

    static {
        XMLUnit.setControlParser(
            "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
        XMLUnit.setTestParser(
            "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
        XMLUnit.setIgnoreWhitespace(true);
    }

    private static final XmlaTestContext context = new XmlaTestContext();

    private static final String DATA_SOURCE_INFO_RESPONSE_PROP =
        "data.source.info.response";

    private XmlaHandler handler;
    private MondrianServer server;

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

    // implement TestCase
    protected void setUp() throws Exception {
        super.setUp();
        DiffRepository diffRepos = getDiffRepos();
        diffRepos.setCurrentTestCaseName(getName());
        server = MondrianServer.createWithRepository(
            new StringRepositoryContentFinder(
                context.getDataSourcesString()),
            XmlaTestContext.CATALOG_LOCATOR);
        handler = new XmlaHandler(
            (XmlaHandler.ConnectionFactory) server,
            "xmla");
        XMLUnit.setIgnoreWhitespace(false);
    }

    // implement TestCase
    protected void tearDown() throws Exception {
        DiffRepository diffRepos = getDiffRepos();
        diffRepos.setCurrentTestCaseName(null);
        server.shutdown();
        server = null;
        handler = null;
        super.tearDown();
    }

    private static DiffRepository getDiffRepos() {
        return DiffRepository.lookup(XmlaTest.class);
    }

    protected void runTest() throws Exception {
        if (!MondrianProperties.instance().SsasCompatibleNaming.get()
            && getName().equals("mdschemaLevelsCubeDimRestrictions"))
        {
            // Changes in unique names of hierarchies and levels mean that the
            // output is a different order in the old behavior, and cannot be
            // fixed by a few sed-like comparisons.
            return;
        }
        DiffRepository diffRepos = getDiffRepos();
        String request = diffRepos.expand(null, "${request}");
        String expectedResponse = diffRepos.expand(null, "${response}");

        Properties props = new Properties();
        XmlaTestContext s = new XmlaTestContext();
        String con = s.getConnectString().replaceAll("&amp;","&");
        PropertyList pl = Util.parseConnectString(con);
        pl.remove(RolapConnectionProperties.Jdbc.name());
        pl.remove(RolapConnectionProperties.JdbcUser.name());
        pl.remove(RolapConnectionProperties.JdbcPassword.name());
        props.setProperty(DATA_SOURCE_INFO_RESPONSE_PROP, pl.toString());
        expectedResponse =
            Util.replaceProperties(
                expectedResponse, Util.toMap(props));

        Element requestElem = XmlaUtil.text2Element(
            XmlaTestContext.xmlFromTemplate(
                request, XmlaTestContext.ENV));
        Element responseElem =
            ignoreLastUpdateDate(executeRequest(requestElem));

        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer();
        StringWriter bufWriter = new StringWriter();
        transformer.transform(
            new DOMSource(responseElem), new StreamResult(bufWriter));
        bufWriter.write(Util.nl);
        String actualResponse =
            TestContext.instance().upgradeActual(
                bufWriter.getBuffer().toString());
        try {
            // Start with a purely logical XML diff to avoid test noise
            // from non-determinism in XML generation.
            XMLAssert.assertXMLEqual(expectedResponse, actualResponse);
        } catch (AssertionFailedError e) {
            // In case of failure, re-diff using DiffRepository's comparison
            // method. It may have noise due to physical vs logical structure,
            // but it will maintain the expected/actual, and some IDEs can even
            // display visual diffs.
            diffRepos.assertEquals("response", "${response}", actualResponse);
        }
    }

    private Element ignoreLastUpdateDate(Element element) {
        NodeList elements = element.getElementsByTagName("LAST_SCHEMA_UPDATE");
        for (int i = elements.getLength(); i > 0; i--) {
            blankNode(elements.item(i - 1));
        }
        return element;
    }

    private void blankNode(Node node) {
        node.setTextContent("");
    }

    private Element executeRequest(Element requestElem) {
        ByteArrayOutputStream resBuf = new ByteArrayOutputStream();
        XmlaRequest request =
            new DefaultXmlaRequest(requestElem, null, null, null, null);
        XmlaResponse response =
            new DefaultXmlaResponse(
                resBuf, "UTF-8", Enumeration.ResponseMimeType.SOAP);
        handler.process(request, response);

        return XmlaUtil.stream2Element(
            new ByteArrayInputStream(resBuf.toByteArray()));
    }

    public static TestSuite suite() {
        TestSuite suite = new TestSuite();

        DiffRepository diffRepos = getDiffRepos();

        MondrianProperties properties = MondrianProperties.instance();
        String filePattern = properties.QueryFilePattern.get();

        final Pattern pattern =
            filePattern == null
            ? null
            : Pattern.compile(filePattern);

        List<String> testCaseNames = diffRepos.getTestCaseNames();

        if (pattern != null) {
            Iterator<String> iter = testCaseNames.iterator();
            while (iter.hasNext()) {
                String name = iter.next();
                if (!pattern.matcher(name).matches()) {
                    iter.remove();
                }
            }
        }

        LOGGER.debug("Found " + testCaseNames.size() + " XML/A test cases");

        for (String name : testCaseNames) {
            suite.addTest(new XmlaTest(name));
        }

        suite.addTestSuite(OtherTest.class);

        return suite;
    }

    /**
     * Non diff-based unit tests for XML/A support.
     */
    public static class OtherTest extends TestCase {
        public void testEncodeElementName() {
            final XmlaUtil.ElementNameEncoder encoder =
                XmlaUtil.ElementNameEncoder.INSTANCE;

            assertEquals("Foo", encoder.encode("Foo"));
            assertEquals("Foo_x0020_Bar", encoder.encode("Foo Bar"));

            if (false) // FIXME:
            assertEquals(
                "Foo_x00xx_Bar", encoder.encode("Foo_Bar"));

            // Caching: decode same string, get same string back
            final String s1 = encoder.encode("Abc def");
            final String s2 = encoder.encode("Abc def");
            assertSame(s1, s2);
        }

        /**
         * Unit test for {@link XmlaUtil#chooseResponseMimeType(String)}.
         */
        public void testAccept() {
            // simple
            assertEquals(
                Enumeration.ResponseMimeType.SOAP,
                XmlaUtil.chooseResponseMimeType("application/xml"));

            // deal with ",q=<n>" quality codes by ignoring them
            assertEquals(
                Enumeration.ResponseMimeType.SOAP,
                XmlaUtil.chooseResponseMimeType(
                    "text/html,application/xhtml+xml,"
                    + "application/xml;q=0.9,*/*;q=0.8"));

            // return null if nothing matches
            assertNull(
                XmlaUtil.chooseResponseMimeType(
                    "text/html,application/xhtml+xml"));

            // quality codes all over the place; return JSON because we see
            // it before application/xml
            assertEquals(
                Enumeration.ResponseMimeType.JSON,
                XmlaUtil.chooseResponseMimeType(
                    "text/html;q=0.9,"
                    + "application/xhtml+xml;q=0.9,"
                    + "application/json;q=0.9,"
                    + "application/xml;q=0.9,"
                    + "*/*;q=0.8"));

            // allow application/soap+xml as synonym for application/xml
            assertEquals(
                Enumeration.ResponseMimeType.SOAP,
                XmlaUtil.chooseResponseMimeType(
                    "text/html,application/soap+xml"));

            // allow text/xml as synonym for application/xml
            assertEquals(
                Enumeration.ResponseMimeType.SOAP,
                XmlaUtil.chooseResponseMimeType(
                    "text/html,application/soap+xml"));
        }
    }
}

// End XmlaTest.java
