/*
 * $Id: JDBCLoginService.java 4147 2012-02-01 17:13:24Z kschaefe $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * 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 (at your option) 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.auth;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.InitialContext;

import org.jdesktop.beans.JavaBean;
/**
 * A login service for connecting to SQL based databases via JDBC
 *
 * @author rbair
 */
@JavaBean
public class JDBCLoginService extends LoginService {
    private static final Logger LOG = Logger.getLogger(JDBCLoginService.class
            .getName());
    
    /**
     * The connection to the database
     */
    private Connection conn;
    /**
     * If used, defines the JNDI context from which to get a connection to
     * the data base
     */
    private String jndiContext;
    /**
     * When using the DriverManager to connect to the database, this specifies
     * any additional properties to use when connecting.
     */
    private Properties properties;
    
    /**
     * Create a new JDBCLoginService and initializes it to connect to a
     * database using the given params.
     * @param driver
     * @param url
     */
    public JDBCLoginService(String driver, String url) {
        super(url);
        try {
            Class.forName(driver);
        } catch (Exception e) {
            LOG.log(Level.WARNING, "The driver passed to the " +
                    "JDBCLoginService constructor could not be loaded. " +
                    "This may be due to the driver not being on the classpath", e);
        }
        this.setUrl(url);
    }
    
    /**
     * Create a new JDBCLoginService and initializes it to connect to a
     * database using the given params.
     * @param driver
     * @param url
     * @param props
     */
    public JDBCLoginService(String driver, String url, Properties props) {
        super(url);
        try {
            Class.forName(driver);
        } catch (Exception e) {
            LOG.log(Level.WARNING, "The driver passed to the " +
                    "JDBCLoginService constructor could not be loaded. " +
                    "This may be due to the driver not being on the classpath", e);
        }
        this.setUrl(url);
        this.setProperties(props);
    }
    
    /**
     * Create a new JDBCLoginService and initializes it to connect to a
     * database using the given params.
     * @param jndiContext
     */
    public JDBCLoginService(String jndiContext) {
        super(jndiContext);
        this.jndiContext = jndiContext;
    }
    
    /**
     * Default JavaBean constructor
     */
    public JDBCLoginService() {
        super();
    }
    
    /**
     * @return the JDBC connection url
     */
    public String getUrl() {
        return getServer();
    }

    /**
     * @param url set the JDBC connection url
     */
    public void setUrl(String url) {
        String old = getUrl();
        setServer(url);
        firePropertyChange("url", old, getUrl());
    }

    /**
     * @return JDBC connection properties
     */
    public Properties getProperties() {
        return properties;
    }

    /**
     * @param properties miscellaneous JDBC properties to use when connecting
     *        to the database via the JDBC driver
     */
    public void setProperties(Properties properties) {
        Properties old = getProperties();
        this.properties = properties;
        firePropertyChange("properties", old, getProperties());
    }
    
    public Connection getConnection() {
        return conn;
    }
    
    public void setConnection(Connection conn) {
        Connection old = getConnection();
        this.conn = conn;
        firePropertyChange("connection", old, getConnection());
    }
    
    /**
     * Attempts to get a JDBC Connection from a JNDI javax.sql.DataSource, using
     * that connection for interacting with the database.
     * @throws Exception
     */
    private void connectByJNDI(String userName, char[] password) throws Exception {
        InitialContext ctx = new InitialContext();
        try {
            javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup(jndiContext);
            conn = ds.getConnection(userName, new String(password));
            conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
        } finally {
            ctx.close();
        }
    }
    
    /**
     * Attempts to get a JDBC Connection from a DriverManager. If properties
     * is not null, it tries to connect with those properties. If that fails,
     * it then attempts to connect with a user name and password. If that fails,
     * it attempts to connect without any credentials at all.
     * <p>
     * If, on the other hand, properties is null, it first attempts to connect
     * with a username and password. Failing that, it tries to connect without
     * any credentials at all.
     * @throws Exception
     */
    private void connectByDriverManager(String userName, char[] password) throws Exception {
        if (getProperties() != null) {
            try {
                conn = DriverManager.getConnection(getUrl(), getProperties());
                conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
            } catch (Exception e) {
                try {
                    conn = DriverManager.getConnection(getUrl(), userName, new String(password));
                    conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                } catch (Exception ex) {
                    conn = DriverManager.getConnection(getUrl());
                    conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                }
            }
        } else {
            try {
                conn = DriverManager.getConnection(getUrl(), userName, new String(password));
            } catch (Exception e) {
                LOG.log(Level.WARNING, "Connection with properties failed. " +
                                "Tryint to connect without.", e);
                //try to connect without using the userName and password
                conn = DriverManager.getConnection(getUrl());

            }
        }
    }

    /**
     * @param name user name
     * @param password user password
     * @param server Must be either a valid JDBC URL for the type of JDBC driver you are using,
     * or must be a valid JNDIContext from which to get the database connection
     */
    @Override
    public boolean authenticate(String name, char[] password, String server) throws Exception {
        //try to form a connection. If it works, conn will not be null
        //if the jndiContext is not null, then try to get the DataSource to use
        //from jndi
        if (jndiContext != null) {
            try {
                connectByJNDI(name, password);
            } catch (Exception e) {
                try {
                    connectByDriverManager(name, password);
                } catch (Exception ex) {
                    LOG.log(Level.WARNING, "Login failed", ex);
                    //login failed
                    return false;
                }
            }
        } else {
            try {
                connectByDriverManager(name, password);
            } catch (Exception ex) {
                LOG.log(Level.WARNING, "", ex);
                return false;
            }
        }
        return true;
    }
}
