/*
 * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package test.auctionportal;

import static jaxp.library.JAXPTestUtilities.setSystemProperty;

import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
import static jaxp.library.JAXPTestUtilities.USER_DIR;
import static jaxp.library.JAXPTestUtilities.compareDocumentWithGold;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static test.auctionportal.HiBidConstants.GOLDEN_DIR;
import static test.auctionportal.HiBidConstants.JAXP_SCHEMA_LANGUAGE;
import static test.auctionportal.HiBidConstants.JAXP_SCHEMA_SOURCE;
import static test.auctionportal.HiBidConstants.SP_ENTITY_EXPANSION_LIMIT;
import static test.auctionportal.HiBidConstants.SP_MAX_OCCUR_LIMIT;
import static test.auctionportal.HiBidConstants.XML_DIR;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import org.xml.sax.SAXParseException;

/**
 * This is a test class for the Auction portal HiBid.com.
 */
/*
 * @test
 * @library /javax/xml/jaxp/libs
 * @run testng/othervm -DrunSecMngr=true test.auctionportal.AuctionItemRepository
 * @run testng/othervm test.auctionportal.AuctionItemRepository
 */
@Listeners({jaxp.library.FilePolicy.class})
public class AuctionItemRepository {
    /**
     * XML file for parsing.
     */
    private final static String ENTITY_XML = XML_DIR + "entity.xml";

    /**
     * Feature name.
     */
    private final static String FEATURE_NAME = "http://xml.org/sax/features/namespace-prefixes";

    /**
     * Setting the EntityExpansion Limit to 128000 and checks if the XML
     * document that has more than two levels of entity expansion is parsed or
     * not. Previous system property was changed to jdk.xml.entityExpansionLimit
     * see http://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html.
     *
     * @throws Exception If any errors occur.
     */
    @Test
    public void testEntityExpansionSAXPos() throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // Secure processing will limit XML processing to conform to
        // implementation limits.
        factory.setFeature(FEATURE_SECURE_PROCESSING, true);
        // Set entityExpansionLimit as 2 should expect fatalError
        setSystemProperty(SP_ENTITY_EXPANSION_LIMIT, String.valueOf(128000));
        SAXParser parser = factory.newSAXParser();

        MyErrorHandler fatalHandler = new MyErrorHandler();
        parser.parse(new File(ENTITY_XML), fatalHandler);
        assertFalse(fatalHandler.isAnyError());
    }
    /**
     * Setting the EntityExpansion Limit to 2 and checks if the XML
     * document that has more than two levels of entity expansion is parsed or
     * not. Previous system property was changed to jdk.xml.entityExpansionLimit
     * see http://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html.
     *
     * @throws Exception If any errors occur.
     */
    @Test(expectedExceptions = SAXParseException.class)
    public void testEntityExpansionSAXNeg() throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // Secure processing will limit XML processing to conform to
        // implementation limits.
        factory.setFeature(FEATURE_SECURE_PROCESSING, true);
        // Set entityExpansionLimit as 2 should expect SAXParseException.
        setSystemProperty(SP_ENTITY_EXPANSION_LIMIT, String.valueOf(2));

        SAXParser parser = factory.newSAXParser();
        MyErrorHandler fatalHandler = new MyErrorHandler();
        parser.parse(new File(ENTITY_XML), fatalHandler);
    }

    /**
     * Testing set MaxOccursLimit to 10000 in the secure processing enabled for
     * SAXParserFactory.
     *
     * @throws Exception If any errors occur.
     */
    @Test
    public void testMaxOccurLimitPos() throws Exception {
        String schema_file = XML_DIR + "toys.xsd";
        String xml_file = XML_DIR + "toys.xml";
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setValidating(true);
        factory.setFeature(FEATURE_SECURE_PROCESSING, true);
        setSystemProperty(SP_MAX_OCCUR_LIMIT, String.valueOf(10000));
        SAXParser parser = factory.newSAXParser();
        parser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA_NS_URI);
        parser.setProperty(JAXP_SCHEMA_SOURCE, new File(schema_file));
        try (InputStream is = new FileInputStream(xml_file)) {
            MyErrorHandler eh = new MyErrorHandler();
            parser.parse(is, eh);
            assertFalse(eh.isAnyError());
        }
    }

    /**
     * Use a DocumentBuilder to create a DOM object and see if Secure Processing
     * feature affects the entity expansion.
     *
     * @throws Exception If any errors occur.
     */
    @Test
    public void testEntityExpansionDOMPos() throws Exception  {
        DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
        dfactory.setFeature(FEATURE_SECURE_PROCESSING, true);
        setSystemProperty(SP_ENTITY_EXPANSION_LIMIT, String.valueOf(10000));
        DocumentBuilder dBuilder = dfactory.newDocumentBuilder();
        MyErrorHandler eh = new MyErrorHandler();
        dBuilder.setErrorHandler(eh);
        dBuilder.parse(ENTITY_XML);
        assertFalse(eh.isAnyError());
    }

    /**
     * Use a DocumentBuilder to create a DOM object and see how does the Secure
     * Processing feature and entityExpansionLimit value affects output.
     * Negative test that when entityExpansionLimit is too small.
     *
     * @throws Exception If any errors occur.
     */
    @Test(expectedExceptions = SAXParseException.class)
    public void testEntityExpansionDOMNeg() throws Exception {
        DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
        dfactory.setFeature(FEATURE_SECURE_PROCESSING, true);
        setSystemProperty(SP_ENTITY_EXPANSION_LIMIT, String.valueOf(2));
        DocumentBuilder dBuilder = dfactory.newDocumentBuilder();
        MyErrorHandler eh = new MyErrorHandler();
        dBuilder.setErrorHandler(eh);
        dBuilder.parse(ENTITY_XML);
    }

    /**
     * Test xi:include with a SAXParserFactory.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXIncludeSAXPos() throws Exception {
        String resultFile = USER_DIR + "doc_xinclude.out";
        String goldFile = GOLDEN_DIR + "doc_xincludeGold.xml";
        String xmlFile = XML_DIR + "doc_xinclude.xml";

        try(FileOutputStream fos = new FileOutputStream(resultFile)) {
            XInclHandler xh = new XInclHandler(fos, null);
            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setNamespaceAware(true);
            spf.setXIncludeAware(true);
            spf.setFeature(FEATURE_NAME, true);
            spf.newSAXParser().parse(new File(xmlFile), xh);
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test the simple case of including a document using xi:include using a
     * DocumentBuilder.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXIncludeDOMPos() throws Exception {
        String resultFile = USER_DIR + "doc_xincludeDOM.out";
        String goldFile = GOLDEN_DIR + "doc_xincludeGold.xml";
        String xmlFile = XML_DIR + "doc_xinclude.xml";
        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setXIncludeAware(true);
            dbf.setNamespaceAware(true);
            Document doc = dbf.newDocumentBuilder().parse(new File(xmlFile));
            doc.setXmlStandalone(true);
            TransformerFactory.newInstance().newTransformer().
                    transform(new DOMSource(doc), new StreamResult(fos));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test the simple case of including a document using xi:include within a
     * xi:fallback using a DocumentBuilder.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXIncludeFallbackDOMPos() throws Exception {
        String resultFile = USER_DIR + "doc_fallbackDOM.out";
        String goldFile = GOLDEN_DIR + "doc_fallbackGold.xml";
        String xmlFile = XML_DIR + "doc_fallback.xml";
        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setXIncludeAware(true);
            dbf.setNamespaceAware(true);

            Document doc = dbf.newDocumentBuilder().parse(new File(xmlFile));
            doc.setXmlStandalone(true);
            TransformerFactory.newInstance().newTransformer()
                    .transform(new DOMSource(doc), new StreamResult(fos));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test for xi:fallback where the fall back text is parsed as text. This
     * test uses a nested xi:include for the fallback test.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXIncludeFallbackTextPos() throws Exception {
        String resultFile = USER_DIR + "doc_fallback_text.out";
        String goldFile = GOLDEN_DIR + "doc_fallback_textGold.xml";
        String xmlFile = XML_DIR + "doc_fallback_text.xml";
        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setXIncludeAware(true);
            dbf.setNamespaceAware(true);

            Document doc = dbf.newDocumentBuilder().parse(new File(xmlFile));
            doc.setXmlStandalone(true);
            TransformerFactory.newInstance().newTransformer()
                    .transform(new DOMSource(doc), new StreamResult(fos));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test the XPointer element() framework with XInclude.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXpointerElementPos() throws Exception {
        String resultFile = USER_DIR + "doc_xpointer_element.out";
        String goldFile = GOLDEN_DIR + "doc_xpointerGold.xml";
        String xmlFile = XML_DIR + "doc_xpointer_element.xml";
        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setXIncludeAware(true);
            dbf.setNamespaceAware(true);

            DocumentBuilder db = dbf.newDocumentBuilder();

            TransformerFactory.newInstance().newTransformer()
                    .transform(new DOMSource(db.parse(new File(xmlFile))),
                            new StreamResult(fos));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test the XPointer framework with a SAX object.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXPointerPos() throws Exception {
        String resultFile = USER_DIR + "doc_xpointer.out";
        String goldFile = GOLDEN_DIR + "doc_xpointerGold.xml";
        String xmlFile = XML_DIR + "doc_xpointer.xml";

        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setNamespaceAware(true);
            spf.setXIncludeAware(true);
            spf.setFeature(FEATURE_NAME, true);
            // parse the file
            spf.newSAXParser().parse(new File(xmlFile), new XInclHandler(fos, null));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test if xi:include may reference the doc containing the include if the
     * parse type is text.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXIncludeLoopPos() throws Exception {
        String resultFile = USER_DIR + "doc_xinc_loops.out";
        String goldFile = GOLDEN_DIR + "doc_xinc_loopGold.xml";
        String xmlFile = XML_DIR + "doc_xinc_loops.xml";

        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setXIncludeAware(true);
            dbf.setNamespaceAware(true);
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(new File(xmlFile));
            doc.normalizeDocument();
            doc.setXmlStandalone(true);

            TransformerFactory.newInstance().newTransformer()
                    .transform(new DOMSource(doc), new StreamResult(fos));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }

    /**
     * Test if two non nested xi:include elements can include the same document
     * with an xi:include statement.
     *
     * @throws Exception If any errors occur.
     */
    @Test(groups = {"readWriteLocalFiles"})
    public void testXIncludeNestedPos() throws Exception {
        String resultFile = USER_DIR + "schedule.out";
        String goldFile = GOLDEN_DIR + "scheduleGold.xml";
        String xmlFile = XML_DIR + "schedule.xml";

        try (FileOutputStream fos = new FileOutputStream(resultFile)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setXIncludeAware(true);
            dbf.setNamespaceAware(true);

            Document doc = dbf.newDocumentBuilder().parse(new File(xmlFile));
            doc.setXmlStandalone(true);
            TransformerFactory.newInstance().newTransformer()
                    .transform(new DOMSource(doc), new StreamResult(fos));
        }
        assertTrue(compareDocumentWithGold(goldFile, resultFile));
    }
}
