/*
 * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

package org.eclipse.persistence.testing.jaxb.dynamic;

import java.io.InputStream;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.persistence.dynamic.DynamicEntity;
import org.eclipse.persistence.exceptions.JAXBException;
import org.eclipse.persistence.internal.dynamic.DynamicEntityImpl;
import org.eclipse.persistence.internal.dynamic.DynamicEntityImpl.PropertyWrapper;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBHelper;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import junit.framework.TestCase;

public class DynamicJAXBRefreshTestCases extends TestCase {

    private static final String OXM_METADATA = "org/eclipse/persistence/testing/jaxb/dynamic/metadata.xml";
    private static final String XSD_METADATA = "org/eclipse/persistence/testing/jaxb/dynamic/refresh.xsd";
    private static final String XML_RESOURCE_BEFORE = "org/eclipse/persistence/testing/jaxb/dynamic/before.xml";
    private static final String XML_RESOURCE_AFTER = "org/eclipse/persistence/testing/jaxb/dynamic/after.xml";

    public String getName() {
        return "Dynamic JAXB: Metadata Refresh: " + super.getName();
    }

    public void testRefreshOXM() throws Exception {
        ClassLoader classLoader = this.getClass().getClassLoader();

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        InputStream metadataStream = classLoader.getResourceAsStream(OXM_METADATA);
        Document metadataDocument = db.parse(metadataStream);
        metadataStream.close();

        Map<String, Object> props = new HashMap<String, Object>(1);
        props.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadataDocument);

        DynamicJAXBContext jc = DynamicJAXBContextFactory.createContextFromOXM(classLoader, props);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        DynamicEntity controlRoot = jc.newDynamicEntity("org.eclipse.persistence.testing.jaxb.dynamic.Root");
        controlRoot.set("name", "R");

        InputStream xmlBeforeStream = classLoader.getResourceAsStream(XML_RESOURCE_BEFORE);
        DynamicEntity rootBefore = (DynamicEntity) unmarshaller.unmarshal(xmlBeforeStream);
        assertEquals(controlRoot.get("name"), rootBefore.get("name"));

        Element xmlElementElement = (Element) metadataDocument.getElementsByTagNameNS("http://www.eclipse.org/eclipselink/xsds/persistence/oxm", "xml-element").item(0);
        xmlElementElement.setAttribute("name", "after-name");
        JAXBHelper.getJAXBContext(jc).refreshMetadata();

        InputStream xmlAfterStream = classLoader.getResourceAsStream(XML_RESOURCE_AFTER);
        DynamicEntity rootAfter = (DynamicEntity) unmarshaller.unmarshal(xmlAfterStream);
        assertEquals(controlRoot.get("name"), rootAfter.get("name"));
    }

    public void testRefreshSchemaNode() throws Exception {
        ClassLoader classLoader = this.getClass().getClassLoader();

        // Get XSD as Element
        InputStream inputStream = ClassLoader.getSystemResourceAsStream(XSD_METADATA);
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        docFactory.setNamespaceAware(true);
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document xsdDocument = docBuilder.parse(inputStream);
        Element xsdElement = xsdDocument.getDocumentElement();

        DynamicJAXBContext jc = DynamicJAXBContextFactory.createContextFromXSD(xsdElement, null, classLoader, null);

        DynamicEntity controlRoot = jc.newDynamicEntity("Root");
        Exception caughtException = null;
        try {
            controlRoot.set("age", "10");
        } catch (Exception e) {
            caughtException = e;
        }

        // Should have caught an exception because we haven't added 'age' to
        // the metadata yet
        assertNotNull(caughtException);

        // Modify METADATA and REFRESH
        Element newElem = xsdDocument.createElementNS("http://www.w3.org/2001/XMLSchema", "element");
        newElem.setAttribute("name", "age");
        newElem.setAttribute("type", "xs:string");
        Element sequence = (Element) xsdElement.getElementsByTagNameNS("http://www.w3.org/2001/XMLSchema", "sequence").item(0);
        sequence.appendChild(newElem);
        JAXBHelper.getJAXBContext(jc).refreshMetadata();

        caughtException = null;
        try {
            controlRoot.set("age", "10");
        } catch (Exception e) {
            caughtException = e;
        }

        // Should NOT have caught an exception because 'age'
        // is now available
        assertNull(caughtException);
    }

    public void testRefreshSchemaInputStream() throws Exception {
        ClassLoader classLoader = this.getClass().getClassLoader();

        InputStream metadataStream = classLoader.getResourceAsStream(XSD_METADATA);

        DynamicJAXBContext jc = DynamicJAXBContextFactory.createContextFromXSD(metadataStream, null, classLoader, null);

        Exception caughtException = null;
        try {
            JAXBHelper.getJAXBContext(jc).refreshMetadata();
        } catch (Exception e) {
            caughtException = e;
        }

        assertNotNull("Refresh Exception was not thrown as expected.", caughtException);
        assertEquals("Incorrect exception thrown.", JAXBException.class, caughtException.getClass());
        assertEquals("Incorrect exception thrown.", JAXBException.CANNOT_REFRESH_METADATA, ((JAXBException) caughtException).getErrorCode());
    }

}
