Package org.apache.metamodel.jdbc

Source Code of org.apache.metamodel.jdbc.JdbcMetadataLoader

/**
* 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.metamodel.jdbc;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.metamodel.MetaModelException;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.ColumnType;
import org.apache.metamodel.schema.MutableColumn;
import org.apache.metamodel.schema.MutableRelationship;
import org.apache.metamodel.schema.Schema;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.schema.TableType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* {@link MetadataLoader} for JDBC metadata loading.
*/
final class JdbcMetadataLoader implements MetadataLoader {

  private static final Logger logger = LoggerFactory.getLogger(JdbcMetadataLoader.class);

  private final JdbcDataContext _dataContext;
  private final boolean _usesCatalogsAsSchemas;
  private final String _identifierQuoteString;

  // these three sets contains the system identifies of whether specific items
  // have been loaded for tables/schemas. Using system identities avoid having
  // to call equals(...) method etc. while doing lazy loading of these items.
  // Invoking equals(...) would be prone to stack overflows ...
  private final Set<Integer> _loadedRelations;
  private final Set<Integer> _loadedColumns;
  private final Set<Integer> _loadedIndexes;
  private final Set<Integer> _loadedPrimaryKeys;

  public JdbcMetadataLoader(JdbcDataContext dataContext, boolean usesCatalogsAsSchemas, String identifierQuoteString) {
    _dataContext = dataContext;
    _usesCatalogsAsSchemas = usesCatalogsAsSchemas;
    _identifierQuoteString = identifierQuoteString;
    _loadedRelations = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
    _loadedColumns = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
    _loadedIndexes = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
    _loadedPrimaryKeys = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
  }

  @Override
  public void loadTables(JdbcSchema schema) {
    final Connection connection = _dataContext.getConnection();
    try {
      final DatabaseMetaData metaData = connection.getMetaData();

      // Creates string array to represent the table types
      final String[] types = JdbcUtils.getTableTypesAsStrings(_dataContext.getTableTypes());
      loadTables(schema, metaData, types);
    } catch (SQLException e) {
      throw JdbcUtils.wrapException(e, "retrieve table metadata for " + schema.getName());
    } finally {
      _dataContext.close(connection, null, null);
    }
  }

  private void loadTables(JdbcSchema schema, DatabaseMetaData metaData, String[] types) {
    String catalogName = _dataContext.getCatalogName();

    ResultSet rs = null;
    try {
      if (logger.isDebugEnabled()) {
        logger.debug("Querying for table types " + Arrays.toString(types) + " in catalog: " + catalogName
            + ", schema: " + schema.getName());
      }
      if (_usesCatalogsAsSchemas) {
        rs = metaData.getTables(schema.getName(), null, null, types);
      } else {
        rs = metaData.getTables(catalogName, schema.getName(), null, types);
      }
      schema.clearTables();
      int tableNumber = -1;
      while (rs.next()) {
        tableNumber++;
        String tableCatalog = rs.getString(1);
        String tableSchema = rs.getString(2);
        String tableName = rs.getString(3);
        String tableTypeName = rs.getString(4);
        TableType tableType = TableType.getTableType(tableTypeName);
        String tableRemarks = rs.getString(5);

        if (logger.isDebugEnabled()) {
          logger.debug("Found table: tableCatalog=" + tableCatalog + ",tableSchema=" + tableSchema
              + ",tableName=" + tableName);
        }

        if (tableSchema == null) {
          tableSchema = tableCatalog;
        }

        JdbcTable table = new JdbcTable(tableName, tableType, schema, this);
        table.setRemarks(tableRemarks);
        table.setQuote(_identifierQuoteString);
        schema.addTable(table);
      }

      final int tablesReturned = tableNumber + 1;
      if (tablesReturned == 0) {
        logger.info("No table metadata records returned for schema '{}'", schema.getName());
      } else {
        logger.debug("Returned {} table metadata records for schema '{}'", new Object[] { tablesReturned,
            schema.getName() });
      }

    } catch (SQLException e) {
      throw JdbcUtils.wrapException(e, "retrieve table metadata for " + schema.getName());
    } finally {
      _dataContext.close(null, rs, null);
    }
  }

  @Override
  public void loadIndexes(JdbcTable table) {
    final int identity = System.identityHashCode(table);
    if (_loadedIndexes.contains(identity)) {
      return;
    }
    synchronized (this) {
      if (_loadedIndexes.contains(identity)) {
        return;
      }

      final Connection connection = _dataContext.getConnection();
      try {
        DatabaseMetaData metaData = connection.getMetaData();
        loadIndexes(table, metaData);
        _loadedIndexes.add(identity);
      } catch (SQLException e) {
        throw JdbcUtils.wrapException(e, "load indexes");
      } finally {
        _dataContext.close(connection, null, null);
      }
    }
  }

  @Override
  public void loadPrimaryKeys(JdbcTable table) {
    final int identity = System.identityHashCode(table);
    if (_loadedPrimaryKeys.contains(identity)) {
      return;
    }
    synchronized (this) {
      if (_loadedPrimaryKeys.contains(identity)) {
        return;
      }
      final Connection connection = _dataContext.getConnection();
      try {
        DatabaseMetaData metaData = connection.getMetaData();
        loadPrimaryKeys(table, metaData);
        _loadedPrimaryKeys.add(identity);
      } catch (SQLException e) {
        throw JdbcUtils.wrapException(e, "load primary keys");
      } finally {
        _dataContext.close(connection, null, null);
      }
    }
  }

  private void loadPrimaryKeys(JdbcTable table, DatabaseMetaData metaData) throws MetaModelException {
    Schema schema = table.getSchema();
    ResultSet rs = null;

    try {
      if (_usesCatalogsAsSchemas) {
        rs = metaData.getPrimaryKeys(schema.getName(), null, table.getName());
      } else {
        rs = metaData.getPrimaryKeys(_dataContext.getCatalogName(), schema.getName(), table.getName());
      }
      while (rs.next()) {
        String columnName = rs.getString(4);
        if (columnName != null) {
          MutableColumn column = (MutableColumn) table.getColumnByName(columnName);
          if (column != null) {
            column.setPrimaryKey(true);
          } else {
            logger.error("Indexed column \"{}\" could not be found in table: {}", columnName, table);
          }
        }
      }
    } catch (SQLException e) {
      throw JdbcUtils.wrapException(e, "retrieve primary keys for " + table.getName());
    } finally {
      _dataContext.close(null, rs, null);
    }
  }

  private void loadIndexes(Table table, DatabaseMetaData metaData) throws MetaModelException {
    Schema schema = table.getSchema();
    ResultSet rs = null;
    // Ticket #170: IndexInfo is nice-to-have, not need-to-have, so
    // we will do a nice failover on SQLExceptions
    try {
      if (_usesCatalogsAsSchemas) {
        rs = metaData.getIndexInfo(schema.getName(), null, table.getName(), false, true);
      } else {
        rs = metaData.getIndexInfo(_dataContext.getCatalogName(), schema.getName(), table.getName(), false,
            true);
      }
      while (rs.next()) {
        String columnName = rs.getString(9);
        if (columnName != null) {
          MutableColumn column = (MutableColumn) table.getColumnByName(columnName);
          if (column != null) {
            column.setIndexed(true);
          } else {
            logger.error("Indexed column \"{}\" could not be found in table: {}", columnName, table);
          }
        }
      }
    } catch (SQLException e) {
      throw JdbcUtils.wrapException(e, "retrieve index information for " + table.getName());
    } finally {
      _dataContext.close(null, rs, null);
    }
  }

  /**
   * Loads column metadata (no indexes though) for a table
   *
   * @param table
   */
  @Override
  public void loadColumns(JdbcTable table) {
    final int identity = System.identityHashCode(table);
    if (_loadedColumns.contains(identity)) {
      return;
    }
    synchronized (this) {
      if (_loadedColumns.contains(identity)) {
        return;
      }

      final Connection connection = _dataContext.getConnection();
      try {
        DatabaseMetaData metaData = connection.getMetaData();
        loadColumns(table, metaData);
        _loadedColumns.add(identity);
      } catch (Exception e) {
        logger.error("Could not load columns for table: " + table, e);
      } finally {
        _dataContext.close(connection, null, null);
      }
    }
  }

  private void loadColumns(JdbcTable table, DatabaseMetaData metaData) {
    final Schema schema = table.getSchema();
    ResultSet rs = null;
    try {
      if (logger.isDebugEnabled()) {
        logger.debug("Querying for columns in table: " + table.getName());
      }
      int columnNumber = -1;
      if (_usesCatalogsAsSchemas) {
        rs = metaData.getColumns(schema.getName(), null, table.getName(), null);
      } else {
        rs = metaData.getColumns(_dataContext.getCatalogName(), schema.getName(), table.getName(), null);
      }
      while (rs.next()) {
        columnNumber++;
        final String columnName = rs.getString(4);
        if (_identifierQuoteString == null && new StringTokenizer(columnName).countTokens() > 1) {
          logger.warn("column name contains whitespace: \"" + columnName + "\".");
        }

        final int jdbcType = rs.getInt(5);
        final String nativeType = rs.getString(6);
        final Integer columnSize = rs.getInt(7);

        if (logger.isDebugEnabled()) {
          logger.debug("Found column: table=" + table.getName() + ",columnName=" + columnName
              + ",nativeType=" + nativeType + ",columnSize=" + columnSize);
        }

        final ColumnType columnType = _dataContext.getQueryRewriter().getColumnType(jdbcType, nativeType,
            columnSize);

        final int jdbcNullable = rs.getInt(11);
        final Boolean nullable;
        if (jdbcNullable == DatabaseMetaData.columnNullable) {
          nullable = true;
        } else if (jdbcNullable == DatabaseMetaData.columnNoNulls) {
          nullable = false;
        } else {
          nullable = null;
        }

        final String remarks = rs.getString(12);

        final JdbcColumn column = new JdbcColumn(columnName, columnType, table, columnNumber, nullable);
        column.setRemarks(remarks);
        column.setNativeType(nativeType);
        column.setColumnSize(columnSize);
        column.setQuote(_identifierQuoteString);
        table.addColumn(column);
      }

      final int columnsReturned = columnNumber + 1;
      if (columnsReturned == 0) {
        logger.info("No column metadata records returned for table '{}' in schema '{}'", table.getName(),
            schema.getName());
      } else {
        logger.debug("Returned {} column metadata records for table '{}' in schema '{}'", new Object[] {
            columnsReturned, table.getName(), schema.getName() });
      }

    } catch (SQLException e) {
      throw JdbcUtils.wrapException(e, "retrieve table metadata for " + table.getName());
    } finally {
      _dataContext.close(null, rs, null);
    }
  }

  @Override
  public void loadRelations(JdbcSchema schema) {
    final int identity = System.identityHashCode(schema);
    if (_loadedRelations.contains(identity)) {
      return;
    }
    synchronized (this) {
      if (_loadedRelations.contains(identity)) {
        return;
      }
      final Connection connection = _dataContext.getConnection();
      try {
        final Table[] tables = schema.getTables();
        final DatabaseMetaData metaData = connection.getMetaData();
        for (Table table : tables) {
          loadRelations(table, metaData);
        }
        _loadedRelations.add(identity);
      } catch (Exception e) {
        logger.error("Could not load relations for schema: " + schema, e);
      } finally {
        _dataContext.close(connection, null, null);
      }
    }
  }

  private void loadRelations(Table table, DatabaseMetaData metaData) {
    Schema schema = table.getSchema();
    ResultSet rs = null;
    try {
      if (_usesCatalogsAsSchemas) {
        rs = metaData.getImportedKeys(schema.getName(), null, table.getName());
      } else {
        rs = metaData.getImportedKeys(_dataContext.getCatalogName(), schema.getName(), table.getName());
      }
      loadRelations(rs, schema);
    } catch (SQLException e) {
      throw JdbcUtils.wrapException(e, "retrieve imported keys for " + table.getName());
    } finally {
      _dataContext.close(null, rs, null);
    }
  }

  private void loadRelations(ResultSet rs, Schema schema) throws SQLException {
    while (rs.next()) {
      String pkTableName = rs.getString(3);
      String pkColumnName = rs.getString(4);

      Column pkColumn = null;
      Table pkTable = schema.getTableByName(pkTableName);
      if (pkTable != null) {
        pkColumn = pkTable.getColumnByName(pkColumnName);
      }
      if (logger.isDebugEnabled()) {
        logger.debug("Found primary key relation: tableName=" + pkTableName + ",columnName=" + pkColumnName
            + ", matching column: " + pkColumn);
      }

      String fkTableName = rs.getString(7);
      String fkColumnName = rs.getString(8);
      Column fkColumn = null;
      Table fkTable = schema.getTableByName(fkTableName);
      if (fkTable != null) {
        fkColumn = fkTable.getColumnByName(fkColumnName);
      }
      if (logger.isDebugEnabled()) {
        logger.debug("Found foreign key relation: tableName=" + fkTableName + ",columnName=" + fkColumnName
            + ", matching column: " + fkColumn);
      }

      if (pkColumn == null || fkColumn == null) {
        logger.error(
            "Could not find relation columns: pkTableName={},pkColumnName={},fkTableName={},fkColumnName={}",
            new Object[] { pkTableName, pkColumnName, fkTableName, fkColumnName });
        logger.error("pkColumn={}", pkColumn);
        logger.error("fkColumn={}", fkColumn);
      } else {
        MutableRelationship.createRelationship(new Column[] { pkColumn }, new Column[] { fkColumn });
      }
    }
  }

}
TOP

Related Classes of org.apache.metamodel.jdbc.JdbcMetadataLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.