/*
 *  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.synapse.mediators.transform;

import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.axiom.om.*;
import org.apache.axis2.transport.base.BaseConstants;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.TestMessageContextBuilder;
import org.apache.synapse.mediators.Value;
import org.apache.synapse.util.jaxp.*;
import org.apache.synapse.util.xpath.SynapseXPath;

import javax.xml.namespace.QName;
import javax.xml.transform.TransformerFactory;

public class XSLTMediatorTest extends TestCase {
    private static final Class[] sourceBuilderFactories = {
        DOOMSourceBuilderFactory.class,
        StreamSourceBuilderFactory.class,
        AXIOMSourceBuilderFactory.class };

    private static final Class[] resultBuilderFactories = {
        DOOMResultBuilderFactory.class,
        StreamResultBuilderFactory.class,
        AXIOMResultBuilderFactory.class };

    private static final String SOURCE =
        "<m0:CheckPriceRequest xmlns:m0=\"http://services.samples/xsd\">\n" +
        "<m0:Code>String</m0:Code>\n" +
        "</m0:CheckPriceRequest>";

    private static final String ENCLOSING_SOURCE =
        "<m:someOtherElement xmlns:m=\"http://someother\">" +
        SOURCE +
        "</m:someOtherElement>";

    private static final String SOURCE_DYNAMIC_KEY1 =
        "<m0:CheckPriceRequest xmlns:m0=\"http://services.samples/xsd\">\n" +
        "<m0:DynamicXsltKey1>DynamicXsltKey1</m0:DynamicXsltKey1>\n" +
        "</m0:CheckPriceRequest>\n" ;

    private static final String SOURCE_DYNAMIC_KEY2 =
        "<m0:CheckPriceRequest xmlns:m0=\"http://services.samples/xsd\">\n" +
        "<m0:DynamicXsltKey2>DynamicXsltKey2</m0:DynamicXsltKey2>\n" +
        "</m0:CheckPriceRequest>\n" ;

    private static final String SOURCE_STATIC_KEY =
        "<m0:CheckPriceRequest xmlns:m0=\"http://services.samples/xsd\">\n" +
        "<m0:StaticXsltKey>StaticXsltKey</m0:StaticXsltKey>\n" +
        "</m0:CheckPriceRequest>\n" ;

    // Create the test cases for the various transformer factories, source builders and
    // result builders dynamically:
    public static TestSuite suite() {
        TestSuite suite = new TestSuite(XSLTMediatorTest.class);
        addGenericTests(suite, "Xalan", org.apache.xalan.processor.TransformerFactoryImpl.class);
        addGenericTests(suite, "Saxon", net.sf.saxon.TransformerFactoryImpl.class);
        return suite;
    }

    private static void addGenericTests(TestSuite suite, final String processorName,
            final Class<? extends TransformerFactory> transformerFactoryClass) {

        for (final Class sbf : sourceBuilderFactories) {
            for (final Class rbf : resultBuilderFactories) {
                String testName = "test" + processorName + shortName(sbf) + shortName(rbf);
                suite.addTest(new TestCase(testName) {
                    @Override
                    public void runTest() throws Throwable {
                        String oldTransformerFactory =
                            TransformerFactory.newInstance().getClass().getName();
                        System.setProperty(TransformerFactory.class.getName(),
                                transformerFactoryClass.getName());
                        test(sbf, rbf);
                        System.setProperty(TransformerFactory.class.getName(),
                                oldTransformerFactory);
                    }
                });
            }
        }
    }

    private static String shortName(Class clazz) {
        String name = clazz.getName();
        name = name.substring(name.lastIndexOf('.')+1);
        if (name.endsWith("BuilderFactory")) {
            name = name.substring(0, name.length()-14);
        }
        return name;
    }
    
    /**
     * Check that the provided element is the result of the XSL transformation of
     * SOURCE by the stylesheet transform_unittest.xslt.
     *
     * @param node result of the XSLT to be matched
     */
    private void assertQuoteElement(OMNode node) {
        assertTrue(node instanceof OMElement);
        OMElement element = (OMElement)node;

        assertTrue("GetQuote".equals(element.getLocalName()));
        assertTrue("http://www.webserviceX.NET/".equals(element.getNamespace().getNamespaceURI()));

        OMElement symbolElem = element.getFirstElement();
        assertTrue("symbol".equals(symbolElem.getLocalName()));
        assertTrue("http://www.webserviceX.NET/".equals(
                symbolElem.getNamespace().getNamespaceURI()));

        assertTrue("String".equals(symbolElem.getText()));
    }

    public void testTransformXSLTCustomSource() throws Exception {

        // create a new XSLT mediator
        XSLTMediator transformMediator = new XSLTMediator();

        // set xpath condition to select source
        SynapseXPath xpath = new SynapseXPath("//m0:CheckPriceRequest");
        xpath.addNamespace("m0", "http://services.samples/xsd");
        transformMediator.setSource(xpath);

        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");

        MessageContext synCtx = new TestMessageContextBuilder().setRequireAxis2MessageContext(true).addFileEntry
                ("xslt-key", "../../repository/conf/sample/resources/transform/transform_unittest.xslt")
                .setBodyFromString(SOURCE).addTextAroundBody().build();
        transformMediator.mediate(synCtx);

        // validate result
        assertQuoteElement(synCtx.getEnvelope().getBody().getFirstOMChild().getNextOMSibling());
    }

    /**
     * If a source element for transformation is not found, default to soap body
     * @throws Exception if there is an error in test
     */
    public void testTransformXSLTDefaultSource() throws Exception {

        // create a new xslt mediator
        XSLTMediator transformMediator = new XSLTMediator();

        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");

        MessageContext synCtx = new TestMessageContextBuilder().addFileEntry("xslt-key",
                "../../repository/conf/sample/resources/transform/transform_unittest.xslt")
                .setBodyFromString(SOURCE).setRequireAxis2MessageContext(true).addTextAroundBody().build();
        transformMediator.mediate(synCtx);

        // validate result
        assertQuoteElement(synCtx.getEnvelope().getBody().getFirstOMChild().getNextOMSibling());
    }

    public void testTransformXSLTLargeMessagesCSV() throws Exception {

        // create a new switch mediator
        XSLTMediator transformMediator = new XSLTMediator();
        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");

        for (int i=0; i<2; i++) {

            // invoke transformation, with static enveope
            MessageContext synCtx = new TestMessageContextBuilder().addFileEntry("xslt-key",
                    "../../repository/conf/sample/resources/transform/transform_load.xml")
                    .setBodyFromFile("../../repository/conf/sample/resources/transform/message.xml")
                    .addTextAroundBody().setRequireAxis2MessageContext(true).build();
            //MessageContext synCtx = TestUtils.getTestContextForXSLTMediator(SOURCE, props);
            transformMediator.mediate(synCtx);
//            synCtx.getEnvelope().serializeAndConsume(new FileOutputStream("/tmp/out.xml"));
//            System.gc();
//            System.out.println("done : " + i + " :: " + Runtime.getRuntime().freeMemory());
        }
    }

    public void testTransformXSLTLargeMessagesXML() throws Exception {

        // create a new switch mediator
        XSLTMediator transformMediator = new XSLTMediator();
        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");
        for (int i=0; i<2; i++) {

            // invoke transformation, with static enveope
            MessageContext synCtx = new TestMessageContextBuilder().addFileEntry("xslt-key",
                    "../../repository/conf/sample/resources/transform/transform_load_3.xml")
                    .setBodyFromFile("../../repository/conf/sample/resources/transform/message.xml")
                    .addTextAroundBody().setRequireAxis2MessageContext(true).build();
            //MessageContext synCtx = TestUtils.getTestContextForXSLTMediator(SOURCE, props);
            transformMediator.mediate(synCtx);
//            System.gc();
//            System.out.println("done : " + i + " :: " + Runtime.getRuntime().freeMemory());
        }
    }

     public void testSynapse242() throws Exception {

        // create a new switch mediator
         XSLTMediator transformMediator = new XSLTMediator();
        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");

         // invoke transformation, with static enveope
         MessageContext synCtx = new TestMessageContextBuilder().addFileEntry("xslt-key",
                 "../../repository/conf/sample/resources/transform/transform_load_2.xml")
                 .setBodyFromFile("../../repository/conf/sample/resources/transform/med_message.xml")
                 .addTextAroundBody().setRequireAxis2MessageContext(true).build();
         transformMediator.mediate(synCtx);

         // validate result
         OMContainer body = synCtx.getEnvelope().getBody();
         assertTrue(body.getFirstOMChild().getNextOMSibling() instanceof OMElement);
         assertTrue(((OMElement) body.getFirstOMChild().getNextOMSibling()).getText().length() > 0);
    }


    public void testTransformXSLTSmallMessages() throws Exception {

        // create a new switch mediator
        XSLTMediator transformMediator = new XSLTMediator();
        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");

        for (int i=0; i<5; i++) {
            // invoke transformation, with static enveope
            MessageContext synCtx = new TestMessageContextBuilder().addFileEntry("xslt-key",
                    "../../repository/conf/sample/resources/transform/transform_load_2.xml")
                    .setBodyFromFile("../../repository/conf/sample/resources/transform/small_message.xml")
                    .addTextAroundBody().setRequireAxis2MessageContext(true).build();
            //MessageContext synCtx = TestUtils.getTestContextForXSLTMediator(SOURCE, props);
            transformMediator.mediate(synCtx);
            //System.out.println("done : " + i + " :: " + Runtime.getRuntime().freeMemory());
        }
    }

    public void testTransformXSLTCustomSourceNonMainElement() throws Exception {

        // create a new switch mediator
        XSLTMediator transformMediator = new XSLTMediator();

        // set xpath condition to select source
        SynapseXPath xpath = new SynapseXPath("//m0:CheckPriceRequest");
        xpath.addNamespace("m0", "http://services.samples/xsd");
        transformMediator.setSource(xpath);

        // set XSLT transformation URL
        setXsltTransformationURL(transformMediator, "xslt-key");

        // invoke transformation, with static enveope
        MessageContext synCtx = new TestMessageContextBuilder().addFileEntry("xslt-key",
                "../../repository/conf/sample/resources/transform/transform_unittest.xslt")
                .setBodyFromString(ENCLOSING_SOURCE)
                .addTextAroundBody().setRequireAxis2MessageContext(true).build();
        transformMediator.mediate(synCtx);

        // validate result
        OMContainer body = synCtx.getEnvelope().getBody();
        if (body.getFirstOMChild().getNextOMSibling() instanceof OMElement) {

            OMElement someOtherElem = (OMElement) body.getFirstOMChild().getNextOMSibling();
            assertTrue("someOtherElement".equals(someOtherElem.getLocalName()));
            assertTrue("http://someother".equals(someOtherElem.getNamespace().getNamespaceURI()));

            assertQuoteElement(someOtherElem.getFirstOMChild());
        } else {
            fail("Unexpected element found in SOAP body");
        }
    }

    public void testTextEncoding() throws Exception {
        XSLTMediator transformMediator = new XSLTMediator();
        setXsltTransformationURL(transformMediator, "xslt-key");

        MessageContext mc = new TestMessageContextBuilder().addFileEntry("xslt-key",
                "../../repository/conf/sample/resources/transform/encoding_test.xslt")
                .setEnvelopeFromFile("../../repository/conf/sample/resources/transform" +
                        "/encoding_test.xml").setRequireAxis2MessageContext(true).build();

        transformMediator.mediate(mc);

        OMElement resultElement = mc.getEnvelope().getBody().getFirstElement();
        assertEquals(BaseConstants.DEFAULT_TEXT_WRAPPER, resultElement.getQName());
        assertEquals("\u00e0 peine arriv\u00e9s nous entr\u00e2mes dans sa chambre",
                resultElement.getText());
    }
    
    // Test for SYNAPSE-307
    public void testInvalidStylesheet() throws Exception {
        XSLTMediator transformMediator = new XSLTMediator();
        setXsltTransformationURL(transformMediator, "xslt-key");
        MessageContext mc = new TestMessageContextBuilder()
            .addEntry("xslt-key", getClass().getResource("invalid.xslt"))
            .setBodyFromString("<root/>").setRequireAxis2MessageContext(true).build();

        try {
            transformMediator.mediate(mc);
            fail("Expected a SynapseException to be thrown");
        } catch (SynapseException ex) {
            // this is what is expected
        }
    }

    /**
     * Test that the XSLT mediator is able to handle CDATA sections in the
     * source AXIOM tree.
     * This tests for regression against WSCOMMONS-338. It should work with
     * AXIOM versions above 1.2.7.
     *
     * @throws Exception in case of an error in tests
     */
    public void testWithCDATA() throws Exception {
        XSLTMediator transformMediator = new XSLTMediator();
        setXsltTransformationURL(transformMediator, "xslt-key");

        MessageContext mc = new TestMessageContextBuilder()
                .addEntry("xslt-key", getClass().getResource("cdata.xslt")).setRequireAxis2MessageContext(true).build();

        OMFactory factory = OMAbstractFactory.getOMFactory();
        OMElement in = factory.createOMElement(new QName(null, "in"));
        factory.createOMText(in, "test", OMNode.CDATA_SECTION_NODE);
        mc.getEnvelope().getBody().addChild(in);

        transformMediator.mediate(mc);

        OMElement out = mc.getEnvelope().getBody().getFirstElement();
        assertEquals("out", out.getLocalName());
        assertEquals("test", out.getText());
    }

    /**
     * Test that the XSLT mediator is able to handle dynamic keys
     * Xpath expression can be used to generate real key dynamically
     * Multiple xslt files can be loaded
     *
     * @throws Exception Exception in case of an error in tests
     */
    public void testWithStaticDynamicKeys() throws Exception {
        for (int i = 0; i < 3; i++) {
            testMultipleKeys(i);
        }
    }

    /**
     * Test with multiple keys including static and dynamic keys
     *
     * @param num number from 0 to 2
     * @throws Exception Exception in case of an error in tests
     */
    private void testMultipleKeys(int num) throws Exception {

        String xsltKeyValue = null;

        String path;
        SynapseXPath xpath;
        Value xsltKey;
        XSLTMediator transformMediator = new XSLTMediator();

        //default source, xsltFile, and state of key (dynamic or static)
        String source = "";
        String xsltFile = "";
        boolean isDynamicKey = true;

        // based on source, different XSLTFiles can be used
        if (num == 0) {
            source = SOURCE_STATIC_KEY;
            xsltFile = "static_key.xslt";
            xsltKeyValue = "StaticXsltKey";
            isDynamicKey = false;

        } else if (num == 1) {
            source = SOURCE_DYNAMIC_KEY1;
            xsltFile = "dynamic_key_1.xslt";
            xsltKeyValue = "DynamicXsltKey1";
            isDynamicKey = true;
        } else if (num == 2) {
            source = SOURCE_DYNAMIC_KEY2;
            xsltFile = "dynamic_key_2.xslt";
            xsltKeyValue = "DynamicXsltKey2";
            isDynamicKey = true;
        }

        if (isDynamicKey) {
            path = "//m0:CheckPriceRequest/m0:" + xsltKeyValue;
            xpath = new SynapseXPath(path);
            xpath.addNamespace("m0", "http://services.samples/xsd");

            // Create key from dynamic key (xpath)
            xsltKey = new Value(xpath);
            // set XSLT transformation URL (Xpath)
            transformMediator.setXsltKey(xsltKey);
        } else {
            //static key
            path = xsltKeyValue;
            // set XSLT transformation URL (static)
            setXsltTransformationURL(transformMediator, path);
        }
        // Mediate twice for synCtx
        MessageContext synCtx = new TestMessageContextBuilder().addEntry(
                xsltKeyValue, getClass().getResource(xsltFile)).setBodyFromString(
                source).addTextAroundBody().setRequireAxis2MessageContext(true).build();
        transformMediator.mediate(synCtx);

        synCtx = new TestMessageContextBuilder().addEntry(
                xsltKeyValue, getClass().getResource(xsltFile)).setBodyFromString(
                source).addTextAroundBody().setRequireAxis2MessageContext(true).build();
        transformMediator.mediate(synCtx);
    }

    protected static void test(Class sbf, Class rbf) throws Exception {

        XSLTMediator transformMediator = new XSLTMediator();
        Value xsltKey = new Value("xslt-key");
        transformMediator.setXsltKey(xsltKey);

        MessageContext mc = new TestMessageContextBuilder()
            .addEntry("xslt-key", XSLTMediator.class.getResource("identity.xslt"))
            .setRequireAxis2MessageContext(true).build();

        OMFactory factory = OMAbstractFactory.getOMFactory();
        OMElement orgRoot = factory.createOMElement(new QName("root"));
        OMElement orgElement = factory.createOMElement(new QName("urn:mynamespace", "element1"));
        orgElement.setText("test");
        OMAttribute orgAttribute = orgElement.addAttribute("att", "testValue", null);
        orgRoot.addChild(orgElement);

        mc.getEnvelope().getBody().addChild(orgRoot);

        transformMediator.addAttribute(XSLTMediator.SOURCE_BUILDER_FACTORY, sbf.getName());
        transformMediator.addAttribute(XSLTMediator.RESULT_BUILDER_FACTORY, rbf.getName());

        transformMediator.mediate(mc);

        OMElement root = mc.getEnvelope().getBody().getFirstElement();
        assertEquals(orgRoot.getQName(), root.getQName());
        OMElement element = (OMElement)root.getFirstOMChild();
        assertEquals(orgElement.getQName(), element.getQName());
        assertEquals(orgElement.getText(), element.getText());
        assertEquals(orgAttribute, orgElement.getAttribute(orgAttribute.getQName()));
        assertNull(element.getNextOMSibling());
    }

    /**
     * Set XSLT transformation URL
     *
     * @param transformMediator Mediator which need to set key
     * @param xsltKeyValue String key value (static key) to set as a key
     */
    private void setXsltTransformationURL(XSLTMediator transformMediator, String xsltKeyValue) {
        Value xsltKey = new Value(xsltKeyValue);
        transformMediator.setXsltKey(xsltKey);
    }
}
