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

com.novartis.opensource.yada.ConnectionFactory Maven / Gradle / Ivy

/**
 * Copyright 2016 Novartis Institutes for BioMedical Research Inc.
 * Licensed 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 com.novartis.opensource.yada;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPException;

import com.novartis.opensource.yada.util.QueryUtils;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

import org.apache.log4j.Logger;

/**
 * Provides for the creation and disposal of JDBC and SOAP connection objects
 * configured using jndi.
 * 
 * @author David Varon
 * @since 1.0.0
 */
public class ConnectionFactory {
  /**
   * Local logger handle
   */
  private final static Logger l = Logger.getLogger(ConnectionFactory.class);
  
  /**
   * Constant equal to {@value}
   * @since 8.0.0
   */
  public  final static String YADA_APP       = "YADA";
  /**
   * Constant equal to {@value}
   * @since 8.0.0
   */
  private final static String YADA_DS_APP    = "APP";
  /**
   * Constant equal to {@value}
   * @since 8.0.0
   */
  private final static String YADA_DS_SOURCE = "SOURCE";
  /**
   * Constant equal to {@value}
   * @since 8.0.0
   */
  private final static String YADA_DS_CONF   = "CONF";
  
  /**
   * Constant equal to {@value}. Used for retrieving app configs.
   * @since 8.0.0
   */
  private final static String YADA_DS_SQL = "select "
                               + "a.app "+YADA_DS_APP+ ", "
                               + "a.source "+YADA_DS_SOURCE+ ", "
                               + "a.conf "+YADA_DS_CONF+ " "
                               + "from yada_query_conf a "
                               + "where a.app != '"+YADA_APP+"' ";
  /**
   * Constant equal to {@value}. Used for retrieving config for specific app.
   * @since 8.0.0
   */
  private final static String YADA_DS_WHERE = "and a.app = ?";               
  
  /**
   * Constant equal to {@value}. Used for retrieving config for specific YADA index.
   * @since 8.0.0
   */
  private final static String YADA_PROPERTIES_PATH = "YADA.properties.path";
  
  /**
   * Constant equal to {@value}. Default location for {@code YADA.properties} file, in {@code WEB-INF/classes}
   * @since 8.0.0
   */
  private final static String YADA_DEFAULT_PROP_PATH = "/YADA.properties";
  
  
  /**
   * Map of {@link Connection} to JNDI {@link String} to facilitate better
   * logging
   */
  //private Map connectionMap = new HashMap<>();
  
  /**
   * Map of {@link DataSource} objects to their names, as stored in the yada index.
   * @since 8.0.0
   */
  private Map dataSourceMap = new HashMap<>();

  /**
   * Map of {@link String} urls to their names, as stored in the yada index.
   * @since 8.0.0
   */
  private Map wsSourceMap = new HashMap<>();

  /**
   * The singleton instance of the class
   * @since 8.0.0
   */
  private static ConnectionFactory factory = null;
  
  static {
    try 
    {
      ConnectionFactory.getConnectionFactory().createDataSources();
      System.out.println("datasources created successfully");
    } 
    catch (Exception e) 
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  /**
   * Private default constructor prohibits instantiation.
   * @since 8.0.0
   */
  private ConnectionFactory() {  }
  
  
  /**
   * Standard singleton lazy-initializer
   * @return the singleton instance of the class
   * @since 8.0.0
   */
  public synchronized static ConnectionFactory getConnectionFactory() {
    if(factory == null)
      factory = new ConnectionFactory();
    return factory;
  }

 
  /**
   * Creates a generic SOAP connection object to use for communication with an
   * endpoint.
   * 
   * @return a {@link SOAPConnection} object on which to submit a request
   * @throws YADAConnectionException
   *           when unable to obtain a connection
   */
  public SOAPConnection getSOAPConnection()
      throws YADAConnectionException {
    SOAPConnectionFactory factory;
    SOAPConnection connection = null;
    try {
      factory = SOAPConnectionFactory.newInstance();
      connection = factory.createConnection();
    } catch (UnsupportedOperationException e) {
      String msg = "There was a problem obtaining a SOAP ConnectionFactory instance.";
      throw new YADAConnectionException(msg, e);
    } catch (SOAPException e) {
      String msg = "There was a problem obtaining a SOAP Connection with the requested resource.";
      throw new YADAConnectionException(msg, e);
    }

    return connection;
  }
  
  /**
   * Pulls the YADA connection pool out of the {@link #dataSourceMap} and returns a connection.
   * If the datasource has not yet been created, it will be.
   * 
   * @return a {@link Connection} object from the {@link DataSource} connection pool
   * @throws YADAConnectionException
   * @since 8.0.0
   */
  private Connection getYADAConnection() throws YADAConnectionException 
  { 
    Connection yadaConn = null;
    HikariDataSource yadaDs = this.getDataSourceMap().get(YADA_APP);
    if(yadaDs == null)
    {
      String path = System.getProperty(YADA_PROPERTIES_PATH);
      if(path == null || "".equals(path))
        path = YADA_DEFAULT_PROP_PATH;
      
      HikariConfig config = new HikariConfig(path);
      yadaDs = new HikariDataSource(config);
      this.getDataSourceMap().put(YADA_APP, yadaDs);
    }
    try 
    {
      yadaConn = yadaDs.getConnection();
    } 
    catch (SQLException e) 
    {
      String msg = "Could not retrieve connection from datasource.";
      throw new YADAConnectionException(msg, e);
    }
    return yadaConn;
  }
  
  /**
   * Called from an initializer or static block, this method will retrieve the datasource configs from
   * the YADA index and store them in the {@link #dataSourceMap}.
   * @throws YADAConnectionException
   * @since 8.0.0 
   */
  public void createDataSources() throws YADAConnectionException 
  {
    getYADAConnection();
    Connection yadaConn  = this.getYADAConnection();
    PreparedStatement pstmt;
    Map conf = new HashMap<>();
    ResultSet rs = null;
    try
    {
      pstmt = yadaConn.prepareStatement(YADA_DS_SQL);
    } 
    catch (SQLException e)
    {
      String msg = "Unable to create or configure the PreparedStatement used to lookup the datasource configs in the YADA Index.  This could be a serious configuration issue.";
      throw new YADAConnectionException(msg,e);
    }
    int row = 0;
    try
    {
      rs = pstmt.executeQuery();
      if(!rs.isBeforeFirst())
      {
        String msg = "There was an issue retrieving the app list";
        throw new YADAConnectionException(msg);
      }
      while (rs.next())
      {
        String confStr = rs.getString(YADA_DS_CONF);
        conf.put(YADA_DS_APP, rs.getString(YADA_DS_APP));
        conf.put(YADA_DS_SOURCE, rs.getString(YADA_DS_SOURCE));
        conf.put(YADA_DS_CONF, confStr);
        if(confStr != null)
        {
          if(confStr.matches(QueryUtils.RX_JDBC_CONF))
          {
            this.createJdbcDataSource(conf);
          }
          else
          {
            this.createWsDataSource(conf);
          }
        }
        
        row++;
      }
    }
    catch (SQLException e)
    {
      String msg = "The lookup query caused an error. This could be because the service is misconfigured.";
      throw new YADAConnectionException(msg,e);
    }
    finally 
    {
      releaseResources(rs);
    }
  }

  /**
   * Stores the webservice url in the {@link #wsSourceMap}
   * @param conf the webservice configuration object to parse
   */
  public void createWsDataSource(Map conf)
  {
    String app = conf.get(YADA_DS_APP);
    if(app != null && !"".equals(app))
    {
      if(this.getWsSourceMap().get(app) == null)
      {
        String url = conf.get(YADA_DS_CONF);
        if(url == null || "".equals(url))
          url = conf.get(YADA_DS_SOURCE);
        this.getWsSourceMap().put(app,url);
        l.debug(app+" : "+url);
      }
    }
  }
  
  /**
   * Creates and stores a datasource in the {@link #dataSourceMap}
   * @param conf {@link Map} containing datasource configs from database
   * @since 8.0.0
   */
  public void createJdbcDataSource(Map conf) 
  {
    String app = conf.get(YADA_DS_APP);
    if(app != null && !"".equals(app))
    {
      if(this.getDataSourceMap().get(app) == null)
      {
        Properties props = new Properties();
        String propStr = conf.get(YADA_DS_CONF);
        String lines[] = propStr.split("\\r?\\n");
        for(String line : lines)
        {
          String[] pair = line.split("=",2);
          props.put(pair[0], pair[1]);
        }
        props.put("poolName","HikariPool-"+app);
        
        try
        {
          HikariConfig config = new HikariConfig(props);
          HikariDataSource datasource = new HikariDataSource(config);
          this.getDataSourceMap().put(app, datasource);
        }
        catch(Exception e)
        {
          String msg = "Could not create connection pool for "+app;
          l.warn(msg);
        }
      }
    }
  }

  /**
   * Returns a JDBC connection from the datasource identified by {@code app}. Updated
   * in 8.0.0 to retrieve connections from {@link DataSource} objects stored in {@link #dataSourceMap}
   * rather than the JNDI context.
   * 
   * @param app the name of the datasource
   * @return {@link Connection} object to facilitate query execution
   * @throws YADAConnectionException
   *           when the JNDI {@code source} is unrecognized or the database to
   *           which it refers is unreachable
   */
  public Connection getConnection(String app) throws YADAConnectionException 
  {
    if(app.equals(YADA_APP))
    {
      return getYADAConnection();
    }
    
    Connection connection   = null;
    ResultSet  rs           = null;
    DataSource ds           = this.getDataSourceMap().get(app);
    Map conf = new HashMap<>();
    
    try 
    {  
      if(ds == null)
      {
        
        PreparedStatement pstmt;
        try
        {
          pstmt = getYADAConnection().prepareStatement(YADA_DS_SQL + YADA_DS_WHERE);
          pstmt.setString(1,app);
        } 
        catch (SQLException e)
        {
          String msg = "Unable to create or configure the PreparedStatement used to lookup the requested app in the YADA Index.  This could be a serious configuration issue.";
          throw new YADAConnectionException(msg,e);
        }
        int row = 0;
        try
        {
          rs = pstmt.executeQuery();
          if(!rs.isBeforeFirst())
          {
            String msg = "The requested app ["+app+"] does not exist.";
            throw new YADAConnectionException(msg);
          }
          while (rs.next() && row == 0)
          {
            conf.put(YADA_DS_APP, rs.getString(YADA_DS_APP));
            conf.put(YADA_DS_SOURCE, rs.getString(YADA_DS_SOURCE));
            conf.put(YADA_DS_CONF, rs.getString(YADA_DS_CONF));
            row++;
          }
          this.createJdbcDataSource(conf);
          ds = this.getDataSourceMap().get(app);
        }
        catch (SQLException e)
        {
          String msg = "The lookup query caused an error. This could be because the app ("+app+") is misconfigured or doesn't exist in the YADA Index";
          throw new YADAConnectionException(msg,e);
        }
      }
      connection = ds.getConnection();
      l.debug("app: [" + app + "], product: ["
          + connection.getMetaData().getDatabaseProductName() + "], driver: ["
          + connection.getMetaData().getDriverName() + "]");
//    } catch (NamingException e) {
//      String msg = "There was a problem locating the resource identified by the supplied JNDI path ["
//          + Finder.getYADAJndi() + "] in the initial context.";
//      throw new YADAConnectionException(msg, e);
    } catch (SQLException e) {
      String msg = "There was a problem obtaining a JDBC Connection to ["
          + Finder.getYADAJndi()
          + "]. This could be caused by misconfiguration of the resource, recently changed credentials, or some other issue.";
      throw new YADAConnectionException(msg, e);
    }
    //this.connectionMap.put(connection, conf.get(YADA_DS_SOURCE));
    return connection;
  }

  /**
   * Retrieves the in-memory cache used to store requested {@link YADAQuery}
   * objects.
   * 
   * @param cacheManager
   *          the name of the cache manager, ({@code YADAIndexManager})
   * @param cache
   *          the name of the desired cache ({@code YADAIndex}
   * @return {@link Cache} object
   * @since 4.1.0
   */
  public Cache getCacheConnection(String cacheManager, String cache) {
    CacheManager manager = CacheManager.getCacheManager(cacheManager);
    if (manager == null)
      return null;
    return manager.getCache(cache);
  }

  /**
   * @return the dataSourceMap
   * @since 8.0.0
   */
  public Map getDataSourceMap() {
    return this.dataSourceMap;
  }


  /**
   * @return the wsSourceMap
   * @since 8.0.0
   */
  public Map getWsSourceMap() {
    return wsSourceMap;
  }

  /**
   * Retrieves the {@link Statement} from the {@link ResultSet}, closes the
   * {@code ResultSet}, and then cascades to close the
   * {@link java.sql.Statement} (which in turn will cascade to close its
   * {@link Connection}). IF the {@code Statement} can't be acquired, an attempt
   * is still made to close the {@code ResultSet}
   * 
   * @param rs
   *          the {@link ResultSet} recently iterated
   * @throws YADAConnectionException
   *           when {@code rs} can't be closed
   * @see com.novartis.opensource.yada.ConnectionFactory#releaseResources(Statement)
   */
  public static void releaseResources(ResultSet rs)
      throws YADAConnectionException {
    Statement stmt = null;
    if (rs != null) {
      try {
        stmt = rs.getStatement();
        rs.close();
      } catch (SQLException e) {
        String msg = "There was a problem closing the ResultSet.";
        l.warn(msg);
      }
    }
    if (stmt != null)
      releaseResources(stmt);
  }

  /**
   * Retrieves the {@link Connection} from the {@link java.sql.Statement}
   * parameter, closes the {@link Statement} and then cascades to close the
   * {@link Connection}. If the {@link Connection} can't be acquired, an attempt
   * is still made to close the {@link Statement}
   * 
   * @param stmt
   *          the {@link Statement} recently executed (
   *          {@link java.sql.PreparedStatement} or
   *          {@link java.sql.CallableStatement})
   * @throws YADAConnectionException
   *           when {@code stmt} can't be closed
   */
  public static void releaseResources(Statement stmt)
      throws YADAConnectionException {
    Connection conn = null;
    if (stmt != null) {
      try {
        conn = stmt.getConnection();
        stmt.close();
      } catch (SQLException e) {
        // String msg = "There was a problem closing the Statement.";
        // l.warn(msg);
      }
    }
    if (conn != null)
      releaseResources(conn);
  }

  /**
   * Returns the {@link Connection}, specified by the parameter, to the
   * connection pool.
   * 
   * @param conn
   *          - The {@link Connection} intended to be returned to the pool.
   * @throws YADAConnectionException
   *           when {@code conn} can't be returned to the pool
   */
  public static void releaseResources(Connection conn)
      throws YADAConnectionException {
    if (conn != null) {
//      String source = getConnectionFactory().connectionMap.get(conn);
      try {
        conn.close();
      } catch (SQLException e) {
        String msg = "There was a problem closing the Connection. It may have already been closed.";
        throw new YADAConnectionException(msg, e);
      } finally {
        //getConnectionFactory().connectionMap.remove(conn);
      }
//      l.debug("Database connection to [" + source + "] closed successfully.");
    }
  }

  /**
   * Closes the {@link SOAPConnection}
   * 
   * @param conn
   *          the {@link SOAPConnection} object to close
   * @param source
   *          the url string pointing to the soap endpoint
   * @throws YADAConnectionException
   *           when the connection closing operation fails
   */
  public static void releaseResources(SOAPConnection conn, String source)
      throws YADAConnectionException {
    if (conn != null) {
      try {
        conn.close();
      } catch (SOAPException e) {
        String msg = "There was a problem closing the SOAPConnection. It may have already been closed.";
        throw new YADAConnectionException(msg, e);
      } finally {
        
      }
      l.debug("SOAPconnection to [" + source + "] closed successfully.");
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy