/*
 * 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.derbyTesting.functionTests.tests.jdbcapi;

import java.lang.reflect.Method;
import java.sql.SQLException;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.XADataSource;
import junit.extensions.TestSetup;
import junit.framework.Assert;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.J2EEDataSource;
import org.apache.derbyTesting.junit.JDBCDataSource;
import org.apache.derbyTesting.junit.TestConfiguration;

public class PoolXADSCreateShutdownDBTest extends BaseJDBCTestCase {

    static final String[] ADDITIONAL_DBS = {
        "dscreateconatdb1",
        "dscreateshutdowndb1", 
        "dscreateshutdowndb2",
        "conflict1",
        "conflict2",
        "conflict3",
        "conflict4",
        "conflict5",
        "conflict6",
        "conflict7"
    };
    
    static String DBNotFoundState;

	private String ShutdownState = "08006";
    
    public PoolXADSCreateShutdownDBTest(String name) {
        super(name);
    }

    public static Test suite() 
    {
        BaseTestSuite suite = new BaseTestSuite("PoolXADSCreateShutdownTest");
        Test test = TestConfiguration.defaultSuite(PoolXADSCreateShutdownDBTest.class);        
        //Test test = TestConfiguration.clientServerSuite(DSCreateShutdownDBTest.class);
        suite.addTest(test);
        
        TestSetup setup = TestConfiguration.singleUseDatabaseDecorator(suite);
        // we need a couple extra databases to test they get created
        for (int i = 0; i < ADDITIONAL_DBS.length; i++)
        {
            setup = TestConfiguration.additionalDatabaseDecorator(setup,
                "emb" + ADDITIONAL_DBS[i]);
            setup = TestConfiguration.additionalDatabaseDecorator(setup,
                "srv" + ADDITIONAL_DBS[i]);
        }
    
        return suite;
    }
    
    public void tearDown() throws Exception {
        // attempt to get rid of any databases. 
        // only 5 dbs (in addition to the defaultdb) should actually get
        // created, but just in case...
        TestConfiguration conf = TestConfiguration.getCurrent();
        for (int i = 0; i < ADDITIONAL_DBS.length; i++) {
            removeDirectory(conf.getDatabasePath("emb" + ADDITIONAL_DBS[i]));
            removeDirectory(conf.getDatabasePath("srv" + ADDITIONAL_DBS[i]));
        }
        super.tearDown();
    }

    public void testPoolDS() throws SQLException {
        ConnectionPoolDataSource ds = 
            J2EEDataSource.getConnectionPoolDataSource();
        doCreateAndShutdown(ds);
    }
    
    public void testXADS() throws SQLException {
        XADataSource ds = J2EEDataSource.getXADataSource();
        doCreateAndShutdown(ds);
    }
    
    public void doCreateAndShutdown(Object ds) throws SQLException {
        
        if (usingEmbedded())
            DBNotFoundState = "XJ004";
        else
            DBNotFoundState = "08004"; 
             
   
        
        // first play with default db, which is already created.
        String dbName = 
            TestConfiguration.getCurrent().getDefaultDatabaseName();
        // just check that we really access the database
        assertUpdateCount(createStatement(), 0, "set schema APP");
   
        // check that first the value is null
        assertGetNull(ds, dbName);
        // check that we can set & that when set we can get
        // doesn't actually open connections so a little silly.
        assertSetAndGet(ds, dbName, "shutdownDatabase", "shutdown");
        assertSetAndGet(ds, dbName, "createDatabase", "create");
        // set to an invalid value, should get ignored
        assertNotSetAndGet(ds, dbName, "shutdownDatabase", "boo");
        assertNotSetAndGet(ds, dbName, "createDatabase", "boo");
        assertNotSetAndGet(ds, dbName, "shutdownDatabase", "false");
        assertNotSetAndGet(ds, dbName, "createDatabase", "false");
        
        assertReset(ds, dbName);
        clearBeanProperties(ds);
        // check that create using ConnAttributes works
        assertCreateUsingConnAttrsOK(
            ds, composeDatabaseName(ADDITIONAL_DBS[0]));
        clearBeanProperties(ds);
        // check that shutting down using Attributes works
        assertShutdownUsingConnAttrsOK(ds, dbName);
        clearBeanProperties(ds);
        // now, actually create using setCreateDB, and shutdown a database
        // first ensure it's not there yet
        dbName = composeDatabaseName(ADDITIONAL_DBS[1]);
        
        assertNoDB(ds, dbName);
        // straightforward create and shutdown
        clearBeanProperties(ds);
        assertPositive(ds, dbName);
        clearBeanProperties(ds);
        // what happens when you combine set*Database and 
        // matching connection attribute? (should work)
        dbName = composeDatabaseName(ADDITIONAL_DBS[2]);
        assertNoDB(ds, dbName);
        clearBeanProperties(ds);
        assertTwiceOK(ds, dbName);
        
        clearBeanProperties(ds);
        // the rest of the testing is on conflicted settings
        // the result is not defined, so a change in behavior does not 
        // necessarily indicate a bug, but may be relevant for existing apps
        // what happens when you combine create and shutdown connattr?
        // database does not get created.
        assertShutdownAndCreateConnAttr(DBNotFoundState, ds,  
            composeDatabaseName(ADDITIONAL_DBS[3]), 
            "shutdown=true;create=true");
        clearBeanProperties(ds);
        assertShutdownAndCreateConnAttr(DBNotFoundState, ds, 
            composeDatabaseName(ADDITIONAL_DBS[4]), 
            "create=true;shutdown=true");
        clearBeanProperties(ds);
        // and when you set both setShutdownDatabase and setCreateDatabase?
        // database does not get created
        assertConflictedSettersOK(ds, composeDatabaseName(ADDITIONAL_DBS[5]));
        clearBeanProperties(ds);
        // what happens when you combine set*Database and
        // opposing connection attributes? database does not get created. 
        assertConflictedSetterConnAttrOK(ds);
    }
    
    protected String composeDatabaseName(String dbName) {
        if (usingEmbedded())
            return "emb" + dbName;
        else 
            return "srv" + dbName;
    }
    
    protected void assertGetNull(Object ds, String dbName) throws SQLException {
        assertNull(getBeanProperty(ds, "shutdownDatabase"));
        assertNull(getBeanProperty(ds, "createDatabase"));
    }
    
    protected void assertSetAndGet(
        Object ds, String dbName, String propertyString, String setValue)
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, propertyString, setValue);
        assertEquals(setValue,getBeanProperty(ds, propertyString).toString());
    }
    
    protected void assertNotSetAndGet(
        Object ds, String dbName, String propertyString, String setValue)
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, propertyString, setValue);
        assertNull(getBeanProperty(ds, propertyString));
    }
    
    protected void assertReset(Object ds, String dbName) 
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "");
        assertNull(getBeanProperty(ds, "createDatabase"));
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "create");
        assertEquals("create", getBeanProperty(ds, "createDatabase"));
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "boo");
        assertNull(getBeanProperty(ds, "createDatabase"));
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "create");
        assertEquals("create", getBeanProperty(ds, "createDatabase"));
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "false");
        assertNull(getBeanProperty(ds, "createDatabase"));
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "create");
        assertEquals("create", getBeanProperty(ds, "createDatabase"));
        JDBCDataSource.setBeanProperty(ds, "createDatabase", "");
        assertNull(getBeanProperty(ds, "createDatabase"));
        try { 
            JDBCDataSource.setBeanProperty(ds, "createDatabase", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "");
        assertNull(getBeanProperty(ds, "shutdownDatabase"));
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "shutdown");
        assertEquals("shutdown", getBeanProperty(ds, "shutdownDatabase"));
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "boo");
        assertNull(getBeanProperty(ds, "shutdownDatabase"));
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "false");
        assertNull(getBeanProperty(ds, "shutdownDatabase"));
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "shutdown");
        assertEquals("shutdown", getBeanProperty(ds, "shutdownDatabase"));
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "");
        assertNull(getBeanProperty(ds, "shutdownDatabase"));
        try { 
            JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static Object getBeanProperty(Object ds, String propertyString)
    {
        String getterName = getGetterName(propertyString);

        // Base the type of the setter method from the value's class.

        Object retObject=null;
        try {
            Method getter = ds.getClass().getMethod(getterName, null);
            retObject = getter.invoke(ds, null);
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }
        return retObject;
    }

    private static String getGetterName(String attribute) {
        return "get" + Character.toUpperCase(attribute.charAt(0))
        + attribute.substring(1);
    }
    
    // if the connattr parameter is true, we set both setShutdownDatabase
    // and ConnectionAttribute shutdown=true.
    protected void assertShutdownUsingSetOK(
        Object ds, String dbName, boolean connAttr) throws SQLException {

        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "shutdown");
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        if (connAttr)
            JDBCDataSource.setBeanProperty(
                ds, "ConnectionAttributes", "shutdown=true");
        assertDSConnectionFailed(ShutdownState, ds);
    }
    
    // for completeness' sake, test create=true conn attr.
    protected void assertCreateUsingConnAttrsOK(Object ds, String dbName)
    throws SQLException {
        JDBCDataSource.setBeanProperty(
                ds, "ConnectionAttributes", "create=true");
        JDBCDataSource.setBeanProperty(ds,"databaseName", dbName);
        assertUpdateCount(ds);
        JDBCDataSource.clearStringBeanProperty(ds, "ConnectionAttributes");
        assertShutdownUsingSetOK(ds, dbName, false);
    }
    
    protected void assertShutdownUsingConnAttrsOK(Object ds, String dbName)
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(
            ds, "ConnectionAttributes", "shutdown=true");
        assertDSConnectionFailed(ShutdownState, ds);
    }

    protected void assertShutdownAndCreateConnAttr(
        String expectedSQLState, Object ds, String dbName, String twoPropertyString)
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(
            ds, "ConnectionAttributes", twoPropertyString);
        assertDSConnectionFailed(expectedSQLState, ds);
    }
    
    protected void assertDSConnectionFailed(
        String expectedSQLState, Object ds) throws SQLException {
        try {
            if (ds instanceof javax.sql.ConnectionPoolDataSource)
                ((ConnectionPoolDataSource)ds).getPooledConnection();
            else
                ((XADataSource)ds).getXAConnection();
            fail("expected an sqlexception " + expectedSQLState);
        } catch (SQLException sqle) {
            assertSQLState(expectedSQLState, sqle);
        }
    }    
    
    protected void assertNoDB(Object ds, String dbName) throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        assertDSConnectionFailed(DBNotFoundState, ds);
    }
    
    protected void assertPositive(Object ds, String dbName) 
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(ds, "CreateDatabase", "create");
        // check that the db exists; execute an unnecessary, but harmless, stmt
        assertUpdateCount(ds); 
        clearBeanProperties(ds);
        assertShutdownUsingSetOK(ds, dbName, false);
    }

    protected void assertTwiceOK(Object ds, String dbName) throws SQLException{
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(ds, "CreateDatabase", "create");
        JDBCDataSource.setBeanProperty(
            ds, "ConnectionAttributes", "create=true");
        // check that the db exists; execute an unnecessary, but harmless, stmt
        assertUpdateCount(ds);
        clearBeanProperties(ds);
        
        assertShutdownUsingSetOK(ds, dbName, true);
    }
    
    protected void assertConflictedSettersOK(Object ds, String dbName) 
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(ds, "CreateDatabase", "create");
        JDBCDataSource.setBeanProperty(ds, "shutdownDatabase", "shutdown");
        try {
            if (ds instanceof javax.sql.ConnectionPoolDataSource)
                ((ConnectionPoolDataSource)ds).getPooledConnection();
            else
                ((XADataSource)ds).getXAConnection();
            fail("expected an sqlexception " + DBNotFoundState);
        } catch (SQLException se) {
            assertSQLState(DBNotFoundState, se);
        }
    }

    protected void assertConflictedSetterConnAttrOK(Object ds) 
    throws SQLException {
        assertConSetOK(ds, DBNotFoundState, 
            composeDatabaseName(ADDITIONAL_DBS[6]), 
            "shutdown=true", "CreateDatabase", "create");
        // with the new networkserver methods, this actually works...
        assertConSetOK(ds, DBNotFoundState, 
            composeDatabaseName(ADDITIONAL_DBS[7]),
            "create=true", "ShutdownDatabase", "shutdown");
        assertSetConOK(ds, DBNotFoundState, 
            composeDatabaseName(ADDITIONAL_DBS[8]), 
            "shutdown=true", "CreateDatabase", "create");
        // with the new networkserver methods, this actually works...
        assertSetConOK(ds, DBNotFoundState, 
            composeDatabaseName(ADDITIONAL_DBS[9]),
            "create=true", "ShutdownDatabase", "shutdown");
    }
    
    // first sets setCreate/ShutdownDB, then sets ConnectionAttributes
    protected void assertConSetOK(Object ds, String expectedSQLState, 
        String dbName, String connAttrValue, String setter, String setValue) 
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(ds, setter, setValue);
        JDBCDataSource.setBeanProperty(
            ds, "ConnectionAttributes", connAttrValue);
        // check that the db exists; execute an unnecessary, but harmless, stmt
        try {
            if (ds instanceof javax.sql.ConnectionPoolDataSource)
                ((ConnectionPoolDataSource)ds).getPooledConnection();
            else
                ((XADataSource)ds).getXAConnection();
            fail("expected an sqlexception " + expectedSQLState);
        } catch (SQLException se) {
            assertSQLState(expectedSQLState, se);
        }
        JDBCDataSource.clearStringBeanProperty(ds, setter);
        JDBCDataSource.clearStringBeanProperty(ds, "ConnectionAttributes");
    }

    // sets ConnectionAttributes first, then SetCreate/ShutdownDB
    protected void assertSetConOK(Object ds, String expectedSQLState, 
        String dbName, String connAttrValue, String setter, String setValue) 
    throws SQLException {
        JDBCDataSource.setBeanProperty(ds, "databaseName", dbName);
        JDBCDataSource.setBeanProperty(
            ds, "ConnectionAttributes", connAttrValue);
        JDBCDataSource.setBeanProperty(ds, setter, setValue);
        // check that the db exists; execute an unnecessary, but harmless, stmt
        try {
            
            if (ds instanceof javax.sql.ConnectionPoolDataSource)
                ((ConnectionPoolDataSource)ds).getPooledConnection();
            else
                ((XADataSource)ds).getXAConnection();
            fail("expected an sqlexception " + expectedSQLState);
        } catch (SQLException se) {
            assertSQLState(expectedSQLState, se);
        }
        JDBCDataSource.clearStringBeanProperty(ds, "ConnectionAttributes");
        JDBCDataSource.clearStringBeanProperty(ds, setter);
    }

    protected void assertUpdateCount(Object ds) throws SQLException {        
        if (ds instanceof javax.sql.ConnectionPoolDataSource)
            assertUpdateCount(
                ((ConnectionPoolDataSource)ds).getPooledConnection().getConnection().createStatement(), 0, "set schema APP");
        else
            assertUpdateCount(
                ((XADataSource)ds).getXAConnection().getConnection().createStatement(), 0, "set schema APP");
    }
    
    
    /**
     * Clear bean properties for next test
     * @param ds
     * @throws SQLException
     */
    private void clearBeanProperties(Object ds) throws SQLException {
        JDBCDataSource.clearStringBeanProperty(ds, "createDatabase");
        JDBCDataSource.clearStringBeanProperty(ds, "shutdownDatabase");
        JDBCDataSource.clearStringBeanProperty(ds, "connectionAttributes");
        JDBCDataSource.clearStringBeanProperty(ds, "databaseName");
    }
 
}
