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

net.ontopia.topicmaps.db2tm.JDBCDataSource Maven / Gradle / Ivy

The newest version!
/*
 * #!
 * Ontopia DB2TM
 * #-
 * Copyright (C) 2001 - 2013 The Ontopia Project
 * #-
 * 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 net.ontopia.topicmaps.db2tm;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import net.ontopia.persistence.proxy.ConnectionFactoryIF;
import net.ontopia.persistence.proxy.DefaultConnectionFactory;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.utils.StreamUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * INTERNAL: Data source that reads tables via JDBC.
 */
public class JDBCDataSource implements DataSourceIF {

  // --- define a logging category.
  private static Logger log = LoggerFactory.getLogger(JDBCDataSource.class);

  protected final RelationMapping mapping;
  protected String propfile;

  protected String catalog;
  protected String schemaPattern;
  protected String tableNamePattern;

  protected Connection conn;

  public JDBCDataSource(RelationMapping mapping) {
    this.mapping = mapping;
  }

  public JDBCDataSource(RelationMapping mapping, Connection conn) {
    this.mapping = mapping;
    this.conn = conn;
  }

  protected void setPropertyFile(String propfile) {
    this.propfile = propfile;
  }

  protected Connection getConnection() {
    if (conn == null) {
      try {
        ConnectionFactoryIF cf = 
          new DefaultConnectionFactory(loadProperties(), false);
        conn = cf.requestConnection();
      } catch (Exception t) {
        throw new OntopiaRuntimeException(t);
      }
    }
    return conn;
  }

  @Override
  public void close() {
    if (conn != null) {
      try {
        conn.close();
      } catch (SQLException t) {
        throw new OntopiaRuntimeException(t);
      }
      conn = null;
    }
  }
  
  @Override
  public Collection getRelations() {
    Collection relations = new ArrayList();
    Connection c = getConnection();
    try {
      DatabaseMetaData dbm = c.getMetaData();
      ResultSet rs = dbm.getTables(catalog, schemaPattern, tableNamePattern,
                                   new String[] { "TABLE", "VIEW",
                                                  "SYSTEM TABLE"});
      while (rs.next()) {
        String schema_name = rs.getString(2);
        String table_name = rs.getString(3);

        Relation relation = null;
        if (schema_name != null) {
          relation = mapping.getRelation(schema_name + "." + table_name);
        }
        if (relation == null) {
          relation = mapping.getRelation(table_name);
        }
        if (relation == null) {
          relation = mapping.getRelation(table_name.toLowerCase());
        }

        if (relation != null) {
          relations.add(relation);
        } else {
          log.debug("No mapping found for table '{}' in schema '{}'.", table_name, schema_name);
        }
      }
      rs.close();
    } catch (Throwable t) {
      throw new OntopiaRuntimeException(t);
    }
    return relations;
  }

  @Override
  public TupleReaderIF getReader(String relation) {
    Relation rel = mapping.getRelation(relation);
    if (rel == null) {
      throw new DB2TMException("Unknown relation: " + relation);
    }
    return new TupleReader(rel);
  }

  @Override
  public ChangelogReaderIF getChangelogReader(Changelog changelog, String startOrder) {
    try {
      return new ChangelogReader(changelog, startOrder);
    } catch (SQLException e) {
      throw new OntopiaRuntimeException("Error creating ChangelogReader", e);
    }
  }
  
  @Override
  public String getMaxOrderValue(Changelog changelog) {
    try {
      // get datatypes
      Map cdatatypes = getColumnTypes(changelog.getTable(), conn);
      Integer ocoltypeobj = cdatatypes.get(changelog.getOrderColumn());
      if (ocoltypeobj == null) {
        ocoltypeobj = cdatatypes.get(changelog.getOrderColumn().toUpperCase());
      }
      int ocoltype;
      if (ocoltypeobj != null) {
        ocoltype = ocoltypeobj.intValue();
      } else {
        ocoltype = 12; // varchar
        throw new DB2TMException("Couldn't find data type of order column '" +
                 changelog.getOrderColumn() + "'");
      }
      
      // prepare, bind and execute statement
      StringBuilder sb = new StringBuilder();
      sb.append("select max(");
      sb.append(changelog.getOrderColumn());
      sb.append(") from ");
      sb.append(changelog.getTable());
      String sql = sb.toString();
      log.debug("max order value query: {}", sql);

      PreparedStatement pstm = null;
      ResultSet rs = null;
      try {
        pstm = conn.prepareStatement(sql);
        rs = pstm.executeQuery();
        if(rs.next()) {
          return JDBCUtils.getHighPrecisionString(rs, 1, ocoltype);
        }
      } finally {
        if (rs != null) {
          rs.close();
        }
        if (pstm != null) {
          pstm.close();
        }
      }
      return null;
    } catch (Throwable t) {
      throw new OntopiaRuntimeException(t);
    }
  }    

  /**
   * Returns a map from column names to Integer objects representing
   * the SQL types of the columns.
   */
  private Map getColumnTypes(String table, Connection conn)
    throws SQLException {
    Map ctypes = getColumnTypes(null, table, conn);

    // workaround/hack for Oracle issue. this is safe, but we may want to
    // sanitize it somewhat.
    if (ctypes.isEmpty() && table.indexOf('.') != -1) {
      int pos = table.indexOf('.');
      ctypes = getColumnTypes(table.substring(0, pos),
                              table.substring(pos + 1),
                              conn);
    }
    return ctypes;
  }

  private Map getColumnTypes(String schema,
                                              String table, Connection conn)
    throws SQLException {
    Map datatypes = new HashMap();
    ResultSet rs = conn.getMetaData().getColumns(null, null, table, null);
    try {
      while(rs.next()) {
        // 4: COLUMN_NAME
        // 5: DATA_TYPE
        datatypes.put(rs.getString(4), rs.getInt(5));
      }
    } finally {
      rs.close();
    }

    // sometimes the above doesn't produce any results, because some
    // implementations require uppercase table names here
    if (datatypes.isEmpty()) {
      // try with uppercase
      rs = conn.getMetaData().getColumns(null, null, table.toUpperCase(), null);
      try {
        while(rs.next()) {
          datatypes.put(rs.getString(4), rs.getInt(5));
        }
      } finally {
        rs.close();
      }
    }
    
    return datatypes;
  }
  
  @Override
  public String toString() {
    return "JDBCDataSource[propfile=" + propfile + "]";
  }

  private Properties loadProperties() throws IOException {
    File basedir = mapping.getBaseDirectory();
    InputStream is = StreamUtils.getInputStream(basedir, propfile);
    if (is == null) {
      throw new DB2TMException("Couldn't find properties file '" + propfile +
                               "'");
    }
    
    Properties props = new Properties();
    props.load(is);
    is.close();
    return props;
  }

  private class TupleReader implements TupleReaderIF {

    private PreparedStatement stm;
    private ResultSet rs;
    private int[] coltypes;
    
    private TupleReader(Relation relation) {
      // build sql statement from relation definition
      StringBuilder sb = new StringBuilder();
      sb.append("select r.");
      String[] rcols = relation.getColumns();
      sb.append(StringUtils.join(rcols, ", r."));
      sb.append(" from ");
      sb.append(relation.getName());
      sb.append(" r");

      // add condtion if specified
      String condition = relation.getCondition();
      if (condition != null) {
        sb.append(" where ");
        sb.append(condition);
      }

      String sql = sb.toString();
      log.debug("tuple query: {}", sql);

      // prepare query
      Connection conn = getConnection();
      try {
        stm = conn.prepareStatement(sql);
        stm.setFetchSize(1000);
        rs = stm.executeQuery();

        // get column datatypes
        coltypes = new int[rcols.length];
        ResultSetMetaData md = rs.getMetaData();
        for (int i=0; i < coltypes.length; i++) {
          coltypes[i] = md.getColumnType(i+1);
        }

      } catch (SQLException e) {
        throw new OntopiaRuntimeException("Error in query: " + sql, e);
      }
      
    }

    @Override
    public String[] readNext() {
      try {
        if (rs.next()) {
          String[] result = new String[coltypes.length];
          for (int i=0; i < result.length; i++) {
            result[i] = JDBCUtils.getString(rs, i+1, coltypes[i]);
          }
          return result;
        } else {
          return null;
        }
      } catch (Throwable t) {
        throw new OntopiaRuntimeException(t);
      }
    }

    @Override
    public void close() {
      try {
        if (rs != null) {
          rs.close();
        }
        if (stm != null) {
          stm.close();
        }
      } catch (SQLException t) {
        throw new OntopiaRuntimeException(t);
      }
    }

  }

  private class ChangelogReader implements ChangelogReaderIF {
    private PreparedStatement stm;
    private ResultSet rs;
    private int[] coltypes;
    private int ocoltype;
    
    private int tcix; // tuple start index
    private int ocix;
    
    private ChangelogReader(Changelog changelog, String orderValue)
      throws SQLException {
      // FIXME: require primary key to be specified on both tables
      // add test case for it
      
      // build sql statement from relation and changelog definitions
      Relation relation = changelog.getRelation();
      String[] cpkey = changelog.getPrimaryKey();
      String[] rpkey = relation.getPrimaryKey();
      if (cpkey.length == 0 && rpkey.length > 0) {
        cpkey = rpkey;
      } else if(rpkey.length == 0 && cpkey.length > 0) {
        rpkey = cpkey;
      } else if(rpkey.length == 0 && cpkey.length == 0) {
        throw new DB2TMConfigException("Please specify the primary-key on the relation and/or on the changelog table '" + changelog.getTable() + ".");
      }
      
      String[] rcols = relation.getColumns();

      // use ordering column if no local ordering column
      String localOrderColumn = changelog.getLocalOrderColumn();
      if (localOrderColumn == null) {
        localOrderColumn = changelog.getOrderColumn();
      }
      
      StringBuilder sb = new StringBuilder();
      sb.append("select distinct");
      
      // list primary key of main relation
      for (int i=0; i < rpkey.length; i++) {
        if (i > 0) {
          sb.append(", ");
        }
        sb.append(" r.");
        sb.append(rpkey[i]);
      }
      sb.append(", ");
      
      // list relation columns
      sb.append(replacePrimaryKey(rcols[0], rpkey, cpkey));
      for (int i= 1; i < rcols.length; i++) {
        sb.append(", ");
        sb.append(replacePrimaryKey(rcols[i], rpkey, cpkey));
      }
      // list changelog columns
      sb.append(", c.");
      sb.append(changelog.getOrderColumn());

      this.tcix = rpkey.length;
      this.ocix = tcix+rcols.length+1;

      // nested query to find latest changes
      sb.append(" from");

      sb.append(" (select ");

      String[] cols = new String[cpkey.length];
      for (int i=0; i < cpkey.length; i++) {
        if (changelog.isExpressionColumn(cpkey[i])) {
          cols[i] = changelog.getColumnExpression(cpkey[i]) + " as " + cpkey[i];
        } else {
          cols[i] = " m1." + cpkey[i];
        }
      }
      sb.append(StringUtils.join(cols, ", "));

      sb.append(", m1.");
      sb.append(changelog.getOrderColumn());
      sb.append(" from ");
      sb.append(changelog.getTable());
      sb.append(" m1 ");

      // add changelog table condition
      if (changelog.getCondition() != null) {
        sb.append(" where ");
        sb.append(changelog.getCondition());
      }
      
      
      sb.append(" order by m1." + changelog.getOrderColumn());
      sb.append(") c");

      // then left outer join with data table
      sb.append(" left outer join ");
      sb.append(relation.getName());
      sb.append(" r on (");

      String[] clauses = new String[cpkey.length];
      for (int i=0; i < cpkey.length; i++) {
        clauses[i] = "c." + cpkey[i] + " = r." + rpkey[i];
      }

      sb.append(StringUtils.join(clauses, " and "));

      // add condtion if specified
      String condition = relation.getCondition();
      if (condition != null) {
        sb.append(" and ");
        sb.append(condition);
      }
      
      sb.append(")");
      
      // add order condition
      if (orderValue != null) {
        sb.append(" where");
        sb.append(" c.");
        sb.append(changelog.getOrderColumn());
        sb.append(" > ?");
      }

      // order by clause
      sb.append(" order by");
      for (int i=0; i < cpkey.length; i++) {
        if (i > 0) {
          sb.append(", ");
        }
        sb.append(" c.");
        sb.append(cpkey[i]);
      }
      sb.append(", c.");
      sb.append(changelog.getOrderColumn());

      String sql = sb.toString();
      log.debug("changelog query: {}", sql);

      Connection conn = getConnection();

        // get hold of column data types
        Map rdatatypes = getColumnTypes(relation.getName(), conn);
        if (rdatatypes.isEmpty()) {
          throw new DB2TMInputException("Relation '" + relation.getName() + "' does not exist.");
      }
        coltypes = new int[rcols.length];
        for (int i=0; i < rcols.length; i++) {
          if (rdatatypes.containsKey(rcols[i])) {
            coltypes[i] = rdatatypes.get(rcols[i]).intValue();
          } else if (rdatatypes.containsKey(rcols[i].toUpperCase())) {
            coltypes[i] = rdatatypes.get(rcols[i].toUpperCase()).intValue();
          } else {
            throw new DB2TMInputException("Column '" + rcols[i] + "' in relation '" + relation.getName() + "' does not exist.");
          }
        }
        Map cdatatypes = getColumnTypes(changelog.getTable(), conn);
        if (cdatatypes.isEmpty()) {
          throw new DB2TMInputException("Relation '" + changelog.getTable() + "' does not exist.");
      }
        Integer oct = cdatatypes.get(changelog.getOrderColumn());
        if (oct == null) {
          oct = cdatatypes.get(changelog.getOrderColumn().toUpperCase());
      }
        if (oct == null) {
          throw new DB2TMInputException("Order column '" +
                                        changelog.getOrderColumn() +
                                        "' does not exist");
      }
        ocoltype = oct.intValue();
        
      // FIXME: consider locking strategy. lock table?

      // prepare, bind and execute statement
      this.stm = conn.prepareStatement(sql);

      // order value
      if (orderValue != null) {
        int cix = 1;
        log.debug("changelog order value: {}", orderValue);
        JDBCUtils.setHighPrecisionObject(this.stm, cix, orderValue, ocoltype);
      }
      this.rs = stm.executeQuery();        
    }
    
    private String replacePrimaryKey(String col, String[] rpkey, String[] cpkey) {
      for (int i=0; i < rpkey.length; i++) {
        if (col.equals(rpkey[i])) {
          return "c." + cpkey[i];
        }
      }
      return "r." + col;
    }
    
    @Override
    public String[] readNext() {
      try {
        if (rs.next()) {
          // produce tuple
          String[] result = new String[coltypes.length];
          for (int i=tcix; i < tcix+result.length; i++) {
            result[i-tcix] = JDBCUtils.getString(rs, i+1, coltypes[i-tcix]);
          }
          return result;
        } else {
          return null;
        }
      } catch (Throwable t) {
        throw new OntopiaRuntimeException(t);
      }
    }

    @Override
    public ChangeType getChangeType() {
      try {
        // if the primary key is null, then obviously the row has been
        // deleted
        if (rs.getObject(1) == null) {
          return ChangeType.DELETE;
        }
        
        // otherwise it's an update
        return ChangeType.UPDATE;
      } catch (SQLException e) {
        throw new OntopiaRuntimeException(e);
      }
    }

    @Override
    public String getOrderValue() {
      try {
        return JDBCUtils.getHighPrecisionString(rs, ocix, ocoltype);
      } catch (Throwable t) {
        throw new OntopiaRuntimeException(t);
      }
    }
    
    @Override
    public void close() {
      try {
        if (rs != null) {
          rs.close();
        }
        if (stm != null) {
          stm.close();
        }
      } catch (Throwable t) {
        throw new OntopiaRuntimeException(t);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy