All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.uvm.ccts.common.db.DataSource Maven / Gradle / Ivy

Go to download

A library of useful generic objects and tools consolidated here to simplify all UVM CCTS projects

There is a newer version: 1.1.5
Show newest version
/*
 * Copyright 2015 The University of Vermont and State
 * Agricultural College.  All rights reserved.
 *
 * Written by Matthew B. Storer 
 *
 * This file is part of CCTS Common.
 *
 * CCTS Common is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CCTS Common 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CCTS Common.  If not, see .
 */

package edu.uvm.ccts.common.db;

import edu.uvm.ccts.common.exceptions.BadCredentialsException;
import edu.uvm.ccts.common.exceptions.ConfigurationException;
import edu.uvm.ccts.common.model.AbstractCredentialedObject;
import edu.uvm.ccts.common.model.Credentials;
import edu.uvm.ccts.common.util.PropertiesUtil;
import electric.xml.Element;
import electric.xml.Elements;
import electric.xml.XPath;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.awt.*;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
import java.util.List;

/**
 * Created by mstorer on 7/23/13.
 */
public class DataSource extends AbstractCredentialedObject implements Serializable {
    private static final Log log = LogFactory.getLog(DataSource.class);

    private static final String USER = "user";
    private static final String PASSWORD = "password";
    private static final String DOMAIN = "domain";


    public static DataSource buildFromXml(Element xmlDataSource) throws ConfigurationException {
        return buildFromXml(null, xmlDataSource);
    }

    public static DataSource buildFromXml(String name, Element xmlDataSource) throws ConfigurationException {
        return buildFromXml(name, xmlDataSource, null);
    }

    public static DataSource buildFromXml(Element xmlDataSource, Frame frame) throws ConfigurationException {
        return buildFromXml(null, xmlDataSource, frame);
    }

    public static DataSource buildFromXml(String name, Element xmlDataSource, Frame frame) throws ConfigurationException {
        Properties info = new Properties();
        Elements xmlProperties = xmlDataSource.getElements(new XPath("properties/property"));
        while (xmlProperties.hasMoreElements()) {
            Element xmlProperty = xmlProperties.next();
            info.setProperty(xmlProperty.getAttribute("name"), xmlProperty.getAttribute("value"));
        }

        if (name == null) name = xmlDataSource.getAttribute("name");

        return new DataSource(name,
                xmlDataSource.getAttribute("driver"),
                xmlDataSource.getAttribute("url"),
                xmlDataSource.getAttribute("note"),
                info,
                frame);
    }

    private static boolean shutdown = false;
    private static final transient Map> connMap = new HashMap>();
    private static Thread maint = null;

    public static synchronized boolean startMaintenanceThread(final int countdownMax, final int periodInSeconds) {
        if (maint != null) return false;

        if (countdownMax <= 0)      throw new IllegalArgumentException("countdownMax must be > 0");
        if (periodInSeconds <= 0)   throw new IllegalArgumentException("periodInSeconds must be > 0");

        log.info("initializing maintenance thread with countdown=" + countdownMax + ", period=" + periodInSeconds + " seconds");

        maint = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    int countdown = countdownMax;
                    while (countdown >= 0) {
                        int remainingConnections = closeAbandonedConnections();

                        if (shutdown) {
                            if (remainingConnections == 0) {
                                break;

                            } else {
                                log.info("system has " + remainingConnections + " remaining open connections.  will try " +
                                        countdown + " more times to gracefully stop before pulling the plug.");

                                countdown --;
                            }
                        }

                        Thread.sleep(periodInSeconds * 1000);
                    }

                } catch (InterruptedException e) {
                    // handle silently

                } finally {
                    log.debug("maintenance thread terminated.");
                    closeAllConnections();
                }
            }

            private int closeAbandonedConnections() {
                synchronized(connMap) {
                    int count = 0;

                    List threadIds = new ArrayList();
                    for (Thread thread : Thread.getAllStackTraces().keySet()) {
                        threadIds.add(thread.getId());
                    }

                    for (Map.Entry> e1 : connMap.entrySet()) {
                        String name = e1.getKey();

                        Iterator> iter = e1.getValue().entrySet().iterator();
                        while (iter.hasNext()) {
                            Map.Entry e2 = iter.next();
                            Long threadId = e2.getKey();

                            if ( ! threadIds.contains(threadId) ) {
                                log.debug("killing connection for '" + name + "' on expired thread(" + threadId + ")");

                                try {
                                    Connection conn = e2.getValue();
                                    if ( ! conn.isClosed() ) {
                                        conn.close();
                                    }

                                } catch (Exception e) {
                                    // handle silently

                                } finally {
                                    iter.remove();
                                }

                            } else {
                                // this is an active thread - update active connections count
                                try {
                                    Connection conn = e2.getValue();
                                    if ( ! conn.isClosed() ) {
                                        count ++;
                                    }

                                } catch (Exception e) {
                                    // handle silently
                                }
                            }
                        }
                    }

                    return count;
                }
            }

            private void closeAllConnections() {
                synchronized(connMap) {
                    for (Map.Entry> e1 : connMap.entrySet()) {
                        String name = e1.getKey();

                        Iterator> iter = e1.getValue().entrySet().iterator();
                        while (iter.hasNext()) {
                            Map.Entry e2 = iter.next();
                            Long threadId = e2.getKey();
                            Connection conn = e2.getValue();

                            try {
                                if (conn != null && ! conn.isClosed()) {
                                    log.info("closing connection '" + name + "' on thread(" + threadId + ")");
                                    conn.close();
                                }

                            } catch (SQLException e) {
                                log.error("caught " + e.getClass().getName() + " closing connection for '" + name + "' on thread(" + threadId + ") - " +
                                        e.getMessage(), e);

                            } finally {
                                iter.remove();
                            }
                        }
                    }
                }

                log.info("all data source connections have been closed.");
            }
        });

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                log.info("shutting down data source connections...");
                shutdown = true;

                try {
                    maint.join();

                } catch (InterruptedException e) {
                    log.error("caught " + e.getClass().getName() + " joining maintenance thread - " + e.getMessage(), e);
                }
            }
        });

        maint.start();

        return true;
    }



//////////////////////////////////////////////////////////////////////////////////////////////
// instance methods and vars
//

    private String name;
    private String driver;
    private String url;
    private Properties info;

    public DataSource(String name, String driver, String url, Properties info) throws ConfigurationException {
        this(name, driver, url, null, info);
    }

    public DataSource(String name, String driver, String url, String note, Properties info) throws ConfigurationException {
        this(name, driver, url, note, info, null);
    }

    public DataSource(String name, String driver, String url, String note, Properties info, Frame frame)
            throws ConfigurationException {

        this.name = name;
        this.driver = driver;

        int qmPos = url.indexOf('?');
        if (qmPos >= 0) {
            this.url = url.substring(0, qmPos);
            this.info = info;

            // add URL properties to info unless the URL property is already specified
            for (String s : url.substring(qmPos + 1).split("&")) {
                if (s.indexOf('=') < 0) continue;
                String[] kvPair = s.split("=");
                if ( ! this.info.containsKey(kvPair[0]) ) {
                    if (kvPair.length == 1) this.info.setProperty(kvPair[0], "");
                    else                    this.info.setProperty(kvPair[0], kvPair[1]);
                }
            }

        } else {
            this.url = url;
            this.info = info;
        }

        loadDriver(driver);
        ensureCredentials(note, frame);
    }

    public String getName() {
        return name;
    }

    public String getDriver() {
        return driver;
    }

    public String getUrl() {
        return url;
    }

    public Properties getInfo() {
        return info;
    }

    public Connection getConnection() throws SQLException {
        Connection conn;

        Long threadId = Thread.currentThread().getId();
        synchronized(connMap) {
            Map map = connMap.get(name);
            if (map == null) {
                map = new HashMap();
                connMap.put(name, map);
            }

            conn = map.get(threadId);
            if (conn == null || conn.isClosed()) {
                log.info("establishing connection to '" + name + "' on thread(" + threadId + ")");
                conn = DriverManager.getConnection(url, info);
                map.put(threadId, conn);
            }
        }

        return conn;
    }

    public void close() {
        Long threadId = Thread.currentThread().getId();
        synchronized(connMap) {
            Map map = connMap.get(name);
            if (map != null) {
                try {
                    Connection conn = map.get(threadId);

                    if (conn != null && ! conn.isClosed()) {
                        log.info("closing connection '" + name + "' on thread(" + threadId + ")");
                        conn.close();
                    }

                } catch (SQLException e) {
                    log.error("caught " + e.getClass().getName() + " closing connection for '" + name + "' on thread(" + threadId + ") - " +
                            e.getMessage(), e);

                } finally {
                    map.remove(threadId);
                }
            }
        }
    }

    // used in [mlm-engine] ManagementImpl to ensure the driver is loaded prior to use
    public void loadDriver() throws ConfigurationException {
        loadDriver(driver);
    }


//////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods
//


    @Override
    protected void ensureCredentials(String note, Frame frame) throws ConfigurationException {
        boolean fallback = true;
        String domain = getDomain();

        if (domain != null) {
            DomainCredentialRegistry dcr = DomainCredentialRegistry.getInstance();

            if (dcr.isRegistered(domain)) {
                Credentials cred = dcr.getCredentials(domain);

                String user = getUser();
                if (user == null || user.equalsIgnoreCase(cred.getUser())) {
                    Properties testProps = PropertiesUtil.copy(info);
                    testProps.setProperty(USER, cred.getUser());
                    testProps.setProperty(PASSWORD, cred.getPassword());

                    try {
                        testCredentials(testProps);

                        fallback = false;

                        setUser(cred.getUser());
                        setPassword(cred.getPassword());

                    } catch (BadCredentialsException e) {
                        // handle silently
                    }
                }
            }
        }

        if (fallback) {
            super.ensureCredentials(note, frame);

            if (domain != null) {
                DomainCredentialRegistry.getInstance().register(domain, new Credentials(getUser(), getPassword()));
            }
        }
    }

    @Override
    protected void testCredentials() throws BadCredentialsException, ConfigurationException {
        testCredentials(info);
    }

    private void testCredentials(Properties props) throws BadCredentialsException, ConfigurationException {
        Connection c = null;
        try {
            c = DriverManager.getConnection(url, props);

        } catch (SQLException e) {
            throw new BadCredentialsException(e.getMessage(), e);

        } catch (Exception e) {
            throw new ConfigurationException(e.getMessage(), e);

        } finally {
            try { if (c != null) c.close(); } catch (Exception e) {}
        }
    }

    private void loadDriver(String driver) throws ConfigurationException {
        try {
            Class.forName(driver).newInstance();

        } catch (Exception e) {
            throw new ConfigurationException("invalid driver: " + driver, e);
        }
    }

    @Override
    protected String getCredentialType() {
        return "data source";
    }

    @Override
    protected String getCredentialTarget() {
        return name;
    }

    @Override
    protected String getUser() {
        return info.getProperty(USER);
    }

    @Override
    protected String getPassword() {
        return info.getProperty(PASSWORD);
    }

    private String getDomain() {
        return info.getProperty(DOMAIN);
    }

    @Override
    protected void setUser(String user) {
        if (user == null) info.remove(USER);
        else              info.setProperty(USER, user);
    }

    @Override
    protected void setPassword(String password) {
        if (password == null) info.remove(PASSWORD);
        else                  info.setProperty(PASSWORD, password);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy