/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id:
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.jndi.test;

import junit.framework.Assert;
import org.mockito.MockitoAnnotations;
import org.ow2.jonas.configadmin.AdapterException;
import org.ow2.jonas.configadmin.ConfigurationInfo;
import org.ow2.jonas.jndi.internal.JndiAdapter;
import org.ow2.jonas.jndi.internal.JndiEntriesService;
import org.ow2.jonas.jndi.internal.JndiServiceImpl;
import org.ow2.jonas.jndi.internal.Policy;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Matchers.any;

/**
 * Test case in order to validate the structure of the XML file and the JndiAdapter class
 * @author Jeremy Cazaux
 */
public class JndiTest {

    /**
     * The sample example. It should be valid
     */
    public static String SAMPLE_EXAMPLE = "/ca-jndi-sample-example.xml";

    /**
     * Unicity example. It should be invalid
     */
    public static String UNICITY_EXAMPLE = "/ca-jndi-unicity-test.xml";

    /**
     * Example with a wrong content. It should be invalid
     */
    public static String WRONG_CONTENT_EXAMPLE = "/ca-jndi-wrong-content.xml";

    /**
     * Example with a wrong policy. It should be invalid
     */
    public static String WRONG_POLICY_EXAMPLE = "/ca-jndi-wrong-policy.xml";

    /**
     * Example with a wrong tag order. It should be invalid
     */
    public static String WRONG_TAG_ORDER_EXAMPLE = "/ca-jndi-wrong-tag-order.xml";

    /**
     * Example with a NULL value. It should be invalid
     */
    public static String NULL_VALUE_EXAMPLE = "/ca-jndi-null-value-test.xml";

    /**
     * Example with an empty value. It should be valid
     */
    public static String EMPTY_VALUE_EXAMPLE = "/ca-jndi-empty-value-test.xml";

    /**
     * JNDI entries XML tag
     */
    public static String JNDI_ENTRIES = "jndi-entries";

    /**
     * Key of first entry of the sample example
     */
    public final static String SAMPLE_EXAMPLE_JNDI_KEY_1 = "java:global/jndi/developer";

    /**
     * Value of the first entry of the sample example
     */
    public final static String SAMPLE_EXAMPLE_JNDI_VALUE_1 = "The JOnAS Team";

    /**
     * Type of the first entry of the sample example
     */
    public final static String SAMPLE_EXAMPLE_JNDI_TYPE_1 ="java.lang.String";

    /**
     * Key of second entry of the sample example
     */
    public final static String SAMPLE_EXAMPLE_JNDI_KEY_2 = "java:global/jndi/arch";

    /**
     * Value of the second entry of the sample example
     */
    public final static String SAMPLE_EXAMPLE_JNDI_VALUE_2 = "64";

    /**
     * Type of the second entry of the sample example
     */
    public final static String SAMPLE_EXAMPLE_JNDI_TYPE_2 = "java.lang.Integer";

    /**
     * Key of the entry of the empty value example
     */
    public final static String EMPTY_VALUE_EXAMPLE_JNDI_KEY = "java:global/test/empty";

    /**
     * Value of the entry of the empty value example
     */
    public final static String EMPTY_VALUE_EXAMPLE_JNDI_VALUE = "";

    /**
     * Type of the entry of empty value example
     */
    public final static String EMPTY_VALUE_EXAMPLE_JNDI_TYPE = "java.lang.String";

    /**
     * The JNDI service
     */
    private JndiServiceImpl jndiService;

    /**
     * The JNDI Adapter
     */
    private JndiAdapter jndiAdapter;

    /**
     * Logger
     */
    private static final Log logger = LogFactory.getLog(JndiTest.class);

    /**
     * Initialization 
     */
    @BeforeClass
    public void init() {
        this.jndiService = new JndiServiceImpl();
        this.jndiService.setDefaultPolicy(Policy.STRICT_CONSISTENCY);
        this.jndiAdapter = new JndiAdapter(this.jndiService);
    }

    /**
     * Sample test with a correct XML file
     */
    @Test
    public void sampleValidTest() throws Exception {
        Vector<String> keysExpected = new Vector<String>();
        Vector<Object> valuesExpected = new Vector<Object>();
        Vector<String> typesExpected = new Vector<String>();

        keysExpected.add(SAMPLE_EXAMPLE_JNDI_KEY_1);
        keysExpected.add(SAMPLE_EXAMPLE_JNDI_KEY_2);
        valuesExpected.add(SAMPLE_EXAMPLE_JNDI_VALUE_1);
        valuesExpected.add(SAMPLE_EXAMPLE_JNDI_VALUE_2);
        typesExpected.add(SAMPLE_EXAMPLE_JNDI_TYPE_1);
        typesExpected.add(SAMPLE_EXAMPLE_JNDI_TYPE_2);

        checkConfigurationInfo(getConfigurationInfoSet(SAMPLE_EXAMPLE), keysExpected, valuesExpected, typesExpected,
                Policy.STRICT_CONSISTENCY);


        //mock tests
        List<String> valuesAsString = new ArrayList<String>();
        for (Object value: valuesExpected) {
            valuesAsString.add(String.class.cast(value));
        }

        JndiEntriesService jndiEntriesService = new JndiEntriesService();
        Context context = mock(InitialContext.class);
        when(context.lookup(keysExpected.get(0))).thenThrow(new NamingException());
        when(context.lookup(keysExpected.get(1))).thenThrow(new NamingException());

        jndiEntriesService.setNames(Collections.list(keysExpected.elements()));
        jndiEntriesService.setValues(valuesAsString);
        jndiEntriesService.setTypes(Collections.list(typesExpected.elements()));
        jndiEntriesService.setPolicy("strict-consistency");
        jndiEntriesService.setContext(context);
        jndiEntriesService.start();

        verify(context,times(2)).lookup(any(String.class));
        verify(context,times(1)).lookup(keysExpected.get(0));
        verify(context,times(1)).lookup(keysExpected.get(1));
        verify(context, times(1)).bind(keysExpected.get(0), valuesExpected.get(0));
        verify(context, times(1)).bind(keysExpected.get(1), Integer.valueOf(valuesAsString.get(1)));

        jndiEntriesService.stop();
        verify(context, times(1)).unbind(keysExpected.get(0));
        verify(context, times(1)).unbind(keysExpected.get(1));
    }

    /**
     * Unicity test
     */
    @Test(expectedExceptions = AdapterException.class)
    public void testThatADocumentWithTwoIdenticallyKeyIsInvalid() throws AdapterException {
        getConfigurationInfoSet(UNICITY_EXAMPLE);
    }

    /**
     * Wrong content test
     */
    @Test(expectedExceptions = AdapterException.class)
    public void testThatAWrongContentIsInvalid() throws AdapterException {
        getConfigurationInfoSet(WRONG_CONTENT_EXAMPLE);
    }

    /**
     * Wrong tag order test
     */
    @Test(expectedExceptions = AdapterException.class)
    public void testThatAWrongTagOrderIsInvalid() throws AdapterException {
        getConfigurationInfoSet(WRONG_TAG_ORDER_EXAMPLE);
    }

    /**
     * Wrong policy test
     */
    @Test(expectedExceptions = AdapterException.class)
    public void testThatAWrongPolicyIsInvalid() throws AdapterException {
        getConfigurationInfoSet(WRONG_POLICY_EXAMPLE);
    }

    /**
     * Null value test
     */
    @Test(expectedExceptions = AdapterException.class)
    public void testThatANullValueIsInvalid() throws AdapterException {
        getConfigurationInfoSet(NULL_VALUE_EXAMPLE);
    }

    /**
     * Empty value test
     */
    @Test
    public void testThatAEmptyValueIsValid() throws Exception {
        Vector<String> keysExpected = new Vector<String>();
        Vector<Object> valuesExpected = new Vector<Object>();
        Vector<String> typesExpected = new Vector<String>();

        keysExpected.add(EMPTY_VALUE_EXAMPLE_JNDI_KEY);
        valuesExpected.add(EMPTY_VALUE_EXAMPLE_JNDI_VALUE);
        typesExpected.add(EMPTY_VALUE_EXAMPLE_JNDI_TYPE);

        checkConfigurationInfo(getConfigurationInfoSet(EMPTY_VALUE_EXAMPLE), keysExpected, valuesExpected, typesExpected,
                Policy.LAST_UPDATE);
    }

    /**
     *
     * @param resource The resource available in the classpath
     * @return the set of {@ConfigurationInfo} of the resource
     * @throws AdapterException {@link AdapterException}
     */
    private Set<ConfigurationInfo> getConfigurationInfoSet(final String resource) throws AdapterException {
        return this.jndiAdapter.convert(getJndiNode(resource));
    }

    /**
     * @param resource The resource
     * @return its JNDI Element if found. Otherwise, null.
     */
    private Element getJndiNode(final String resource) {
        InputStream xmlInputStream = getClass().getResourceAsStream(resource);

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        //provide support for XML namespaces
        dbf.setNamespaceAware(true);
        DocumentBuilder db = null;
        try {
            db = dbf.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            logger.error("Can't instantiate a new DocumentBuilder" + e);
            return null;
        }

        Document dom = null;
        if (db != null) {

            try {
                dom = db.parse(xmlInputStream);
            } catch (SAXException e) {
                logger.error("Can't parse the resource " + resource, e);
                return null;
            } catch (IOException e) {
                logger.error("Can't parse the resource " + resource, e);
                return null;
            }

            Element root = dom.getDocumentElement();

            Element jndiElement = (Element) root.getElementsByTagName(JNDI_ENTRIES).item(0);
            if (jndiElement == null) {
                logger.error("Can't get the JNDI node of the XML resource " + resource);
                Assert.fail();
            }
            return jndiElement;
        }
        return null;
    }

    /**
     *
     * @param configurationInfoSet The {@link ConfigurationInfo} to check
     * @param keysExpected The array of keys expected
     * @param valuesExpected The array of values expected
     * @param typesExpected The array of types expected
     */
    public void checkConfigurationInfo(final Set<ConfigurationInfo> configurationInfoSet, final Vector<String> keysExpected,
                                       final Vector<Object> valuesExpected, final Vector<String> typesExpected,
                                       final Policy policyExpected) throws Exception {

        Assert.assertEquals(keysExpected.size(), valuesExpected.size());
        Assert.assertEquals(valuesExpected.size(), typesExpected.size());
        Assert.assertTrue(configurationInfoSet.size() == 1);

        Iterator<ConfigurationInfo> iterator = configurationInfoSet.iterator();
        if (iterator.hasNext())  {
            ConfigurationInfo configurationInfo = iterator.next();
            Map<String, Object> props = configurationInfo.getProperties();

            Vector<String> keys = (Vector<String>) props.get(JndiAdapter.ENTRY_NAME);
            Vector<String> types = (Vector<String>) props.get(JndiAdapter.ENTRY_TYPE);
            Vector<Object> values = (Vector<Object>) props.get(JndiAdapter.ENTRY_VALUE);

            Assert.assertEquals(keys.size(), keysExpected.size());
            Assert.assertEquals(values.size(), valuesExpected.size());
            Assert.assertEquals(types.size(), typesExpected.size());

            Policy policy = JndiEntriesService.getPolicyAsEnum(String.class.cast(props.get(JndiAdapter.POLICY)));

            Assert.assertEquals(policy, policyExpected);
            int size = keysExpected.size();
            for (int i = 0; i < size; i++) {
                Assert.assertEquals(keys.get(i), keysExpected.get(i));
            }
            for (int i = 0; i < size; i++) {
                Assert.assertEquals(values.get(i), valuesExpected.get(i));
            }
            for (int i = 0; i < size; i++) {
                Assert.assertEquals(types.get(i), typesExpected.get(i));
            }
        }

    }

}
