/**
 *
 * 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.openejb.jee;

import junit.framework.TestCase;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;

import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.sax.SAXSource;
import java.io.*;
import java.util.List;
import java.util.Properties;

/**
 * @version $Revision$ $Date$
 */
public class JeeTest extends TestCase {

    /**
     * TODO Doesn't seem there are any asserts here
     *
     * @throws Exception
     */
    public void testEjbJar() throws Exception {
        final String fileName = "ejb-jar-example1.xml";
        //        String fileName = "ejb-jar-empty.xml";

        //        marshalAndUnmarshal(EjbJar.class, fileName);

        final SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setValidating(false);
        final SAXParser parser = factory.newSAXParser();

        final long start = System.currentTimeMillis();

        //        Unmarshaller unmarshaller = new UnmarshallerImpl(Collections.<JAXBMarshaller>singleton(EjbJarJaxB.INSTANCE));
        //        Marshaller marshaller = new MarshallerImpl(Collections.<JAXBMarshaller>singleton(EjbJarJaxB.INSTANCE));
        final JAXBContext ctx = JAXBContextFactory.newInstance(EjbJar.class);
        final Unmarshaller unmarshaller = ctx.createUnmarshaller();
        final Marshaller marshaller = ctx.createMarshaller();

        final NamespaceFilter xmlFilter = new NamespaceFilter(parser.getXMLReader());
        xmlFilter.setContentHandler(unmarshaller.getUnmarshallerHandler());
        unmarshaller.setEventHandler(new TestValidationEventHandler());

        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);
        final String expected = readContent(in);

        final SAXSource source = new SAXSource(xmlFilter, new InputSource(new ByteArrayInputStream(expected.getBytes())));
        final Object object = unmarshaller.unmarshal(source);

        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        marshaller.marshal(object, baos);

        System.out.println("time: " + (System.currentTimeMillis() - start));
    }

    public void testEjbTimeout() throws Exception {
        final String fileName = "ejb-jar-timeout.xml";
        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);

        final Object o = JaxbJavaee.unmarshalJavaee(EjbJar.class, in);

        final EjbJar ejbJar = (EjbJar) o;
        final EnterpriseBean bean = ejbJar.getEnterpriseBean("A");

        assertTrue("The bean A is not a SessionBean", bean instanceof SessionBean);
        final SessionBean sbean = (SessionBean) bean;

        assertNotNull("Unable to get the StatefulTimeout value", sbean.getStatefulTimeout());
    }

    public void testSessionSynchronization() throws Exception {
        final String fileName = "ejb-session-synchronization.xml";
        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);

        final Object o = JaxbJavaee.unmarshalJavaee(EjbJar.class, in);

        final EjbJar ejbJar = (EjbJar) o;
        final EnterpriseBean bean = ejbJar.getEnterpriseBean("TestBean");

        assertTrue("The bean TestBean  is not a SessionBean", bean instanceof SessionBean);
        final SessionBean sbean = (SessionBean) bean;

        assertNotNull("Unable to get the afterBegin value", sbean.getAfterBeginMethod());
        assertNotNull("Unable to get the beforeCompletion value", sbean.getBeforeCompletionMethod());
        assertNotNull("Unable to get the afterCompletion value", sbean.getAfterCompletionMethod());

        assertNotNull("Unable to get the afterBegin value", sbean.getAfterBegin());
        assertNotNull("Unable to get the beforeCompletion value", sbean.getBeforeCompletion());
        assertNotNull("Unable to get the afterCompletion value", sbean.getAfterCompletion());
    }

    public void testAroundTimeout() throws Exception {
        final String fileName = "ejb-jar-aroundtimeout.xml";
        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);

        final Object o = JaxbJavaee.unmarshalJavaee(EjbJar.class, in);

        final EjbJar ejbJar = (EjbJar) o;
        final EnterpriseBean bean = ejbJar.getEnterpriseBean("TestBean");

        assertTrue("The bean TestBean  is not a SessionBean", bean instanceof SessionBean);
        final SessionBean sbean = (SessionBean) bean;

        final AroundTimeout beanAroundTimeout = sbean.getAroundTimeout().get(0);
        assertEquals("aroundTimeout", beanAroundTimeout.getMethodName());

        final AroundTimeout interceptorAroundTimeout = ejbJar.getInterceptors()[0].getAroundTimeout().get(0);
        assertEquals("aroundTimeout", interceptorAroundTimeout.getMethodName());
    }

    public void testTimerSchedule() throws Exception {
        final String fileName = "ejb-jar-timeschedule.xml";
        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);

        final Object o = JaxbJavaee.unmarshalJavaee(EjbJar.class, in);

        final EjbJar ejbJar = (EjbJar) o;
        final EnterpriseBean bean = ejbJar.getEnterpriseBean("TestBean");

        assertTrue("The bean TestBean  is not a SessionBean", bean instanceof SessionBean);
        final SessionBean sbean = (SessionBean) bean;

        final List<Timer> timers = sbean.getTimer();
        assertEquals(1, timers.size());

        final Timer timer = timers.get(0);
        final TimerSchedule timerSchedule = timer.getSchedule();
        assertEquals("10", timerSchedule.getSecond());
        assertEquals("10", timerSchedule.getMinute());
        assertEquals("*", timerSchedule.getDayOfMonth());
        assertEquals("Mon", timerSchedule.getDayOfWeek());
        assertEquals("*", timerSchedule.getHour());
        assertEquals("Nov", timerSchedule.getMonth());
        assertEquals("*", timerSchedule.getYear());

        assertEquals("2010-03-01T13:00:00Z", timer.getStart().toXMLFormat());
        assertEquals("2012-12-11T14:19:00Z", timer.getEnd().toXMLFormat());

        final NamedMethod timeoutMethod = timer.getTimeoutMethod();
        assertEquals("testScheduleMethod", timeoutMethod.getMethodName());
        assertEquals("javax.ejb.Timer", timeoutMethod.getMethodParams().getMethodParam().get(0));

        assertEquals(Boolean.FALSE, timer.getPersistent());
        assertEquals("America/New_York", timer.getTimezone());
        assertEquals("TestInfo", timer.getInfo());
    }

    public void testEjbJarMdb20() throws Exception {
        final String fileName = "ejb-jar-mdb-2.0.xml";
        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);

        final Object o = JaxbJavaee.unmarshalJavaee(EjbJar.class, in);

        final EjbJar ejbJar = (EjbJar) o;

        final MessageDrivenBean bean = (MessageDrivenBean) ejbJar.getEnterpriseBean("MyMdb");

        final Properties properties = bean.getActivationConfig().toProperties();

        assertEquals(4, properties.size());
        /*
        <message-selector>mySelector</message-selector>
        <acknowledge-mode>Auto-acknowledge</acknowledge-mode>
        <message-driven-destination>
        <destination-type>javax.jms.Queue</destination-type>
        <subscription-durability>Durable</subscription-durability>

         */
        assertEquals("mySelector", properties.get("messageSelector"));
        assertEquals("Auto-acknowledge", properties.get("acknowledgeMode"));
        assertEquals("javax.jms.Queue", properties.get("destinationType"));
        assertEquals("Durable", properties.get("subscriptionDurability"));
    }

    public void testApplication() throws Exception {
        marshalAndUnmarshal(Application.class, "application-example.xml", null);
    }

    public void testApplicationClient() throws Exception {
        marshalAndUnmarshal(ApplicationClient.class, "application-client-example.xml", null);
    }

    public void testWar() throws Exception {
        marshalAndUnmarshal(WebApp.class, "web-example.xml", "web-example-expected.xml");
    }

    public void testWar2_3() throws Exception {
        marshalAndUnmarshal(WebApp.class, "web_2.3-example.xml", "web_2.3-example-expected.xml");
    }

    public void testWar5() throws Exception {
        marshalAndUnmarshal(WebApp.class, "web_5-example.xml", "web_5-example-expected.xml");
    }

    public void testTld() throws Exception {
        marshalAndUnmarshal(TldTaglib.class, "tld-example.xml", null);
    }

    public void testRar10() throws Exception {
        final Connector10 c10 = marshalAndUnmarshal(Connector10.class, "connector-1.0-example.xml", null);
        final Connector c = Connector.newConnector(c10);
        final JAXBContext ctx = JAXBContextFactory.newInstance(Connector.class);
        final Marshaller marshaller = ctx.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        marshaller.marshal(c, baos);

        final byte[] bytes = baos.toByteArray();
        final String actual = new String(bytes);

        final String expected;
        final InputStream in2 = this.getClass().getClassLoader().getResourceAsStream("connector-1.0-example-expected-1.6.xml");
        expected = readContent(in2);

        try {
            StaxCompare.compare(expected, actual);
        } catch (final Exception e) {
//            System.out.append(actual);
            writeToTmpFile(bytes, "connector-1.0-example.xml");
            throw e;
        }
    }

    public void testRar15() throws Exception {
        marshalAndUnmarshal(Connector.class, "connector-1.5-example.xml", null);
    }

    public void testRar16() throws Exception {
        marshalAndUnmarshal(Connector.class, "connector-1.6-example.xml", null);
    }

    public void testWebServiceHandlers() throws Exception {
        final QName[] expectedServiceNames = {new QName("http://www.helloworld.org", "HelloService", "ns1"), new QName("http://www.bar.org", "HelloService", "bar"),
            new QName("http://www.bar1.org", "HelloService", "bar"), new QName(XMLConstants.NULL_NS_URI, "HelloService", "foo"), new QName(XMLConstants.NULL_NS_URI, "*"), null};
        try (InputStream in = this.getClass().getClassLoader().getResourceAsStream("handler.xml")) {
            final HandlerChains handlerChains = (HandlerChains) JaxbJavaee.unmarshalHandlerChains(HandlerChains.class, in);
            for (int index = 0; index < handlerChains.getHandlerChain().size(); index++) {
                final HandlerChain handlerChain = handlerChains.getHandlerChain().get(index);
                final QName serviceName = handlerChain.getServiceNamePattern();
                final QName expectedServiceName = expectedServiceNames[index];
                if (expectedServiceName == null) {
                    assertNull(serviceName);
                } else {
                    assertEquals("serviceNamePattern at index " + index + " mismatches", expectedServiceName, serviceName);
                }
            }
            System.out.println(JaxbJavaee.marshal(HandlerChains.class, handlerChains));
        }
    }

    public static <T> T marshalAndUnmarshal(final Class<T> type, final String sourceXmlFile, final String expectedXmlFile) throws Exception {
        final InputStream in = JeeTest.class.getClassLoader().getResourceAsStream(sourceXmlFile);
        final T object = (T) JaxbJavaee.unmarshalJavaee(type, in);
        in.close();
        assertTrue(object.getClass().isAssignableFrom(type));

        final JAXBContext ctx = JAXBContextFactory.newInstance(type);
        final Marshaller marshaller = ctx.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        marshaller.marshal(object, baos);

        final byte[] bytes = baos.toByteArray();
        final String actual = new String(bytes);

        final String expected;
        if (expectedXmlFile == null) {
            final InputStream in2 = JeeTest.class.getClassLoader().getResourceAsStream(sourceXmlFile);
            expected = readContent(in2);
        } else {
            final InputStream in2 = JeeTest.class.getClassLoader().getResourceAsStream(expectedXmlFile);
            expected = readContent(in2);
        }

        try {
            StaxCompare.compare(expected, actual);
        } catch (final Exception e) {
//            System.out.append(actual);
            writeToTmpFile(bytes, sourceXmlFile);
            throw e;
        }
        return object;
    }

    public static class NamespaceFilter extends XMLFilterImpl {

        private static final InputSource EMPTY_INPUT_SOURCE = new InputSource(new ByteArrayInputStream(new byte[0]));

        public NamespaceFilter(final XMLReader xmlReader) {
            super(xmlReader);
        }

        public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
            return EMPTY_INPUT_SOURCE;
        }

        public void startElement(final String uri, final String localName, final String qname, final Attributes atts) throws SAXException {
            super.startElement("http://java.sun.com/xml/ns/javaee", localName, qname, atts);
        }
    }

    private static void writeToTmpFile(final byte[] bytes, final String xmlFileName) {
        try {
            File tempFile = null;
            try {
                tempFile = File.createTempFile("jaxb-output", "xml");
            } catch (final Throwable e) {
                final File tmp = new File("tmp");
                if (!tmp.exists() && !tmp.mkdirs()) {
                    throw new IOException("Failed to create local tmp directory: " + tmp.getAbsolutePath());
                }

                tempFile = File.createTempFile("jaxb-output", "xml", tmp);
            }
            final FileOutputStream out = new FileOutputStream(tempFile);
            out.write(bytes);
            out.close();
            System.out.println("Jaxb output of " + xmlFileName + " written to " + tempFile.getAbsolutePath());
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    private static String readContent(InputStream in) throws IOException {
        final StringBuilder sb = new StringBuilder();
        in = new BufferedInputStream(in);
        try {
            int i = in.read();
            while (i != -1) {
                sb.append((char) i);
                i = in.read();
            }
        } finally {
            in.close();
        }
        return sb.toString();
    }

    private static class TestValidationEventHandler implements ValidationEventHandler {

        public boolean handleEvent(final ValidationEvent validationEvent) {
            System.out.println(validationEvent.getMessage());
            System.out.println(validationEvent.getLocator());
            return false; // if an error occurs we must be aware of
        }
    }
}
