package com.temenos.interaction.jdbc.producer.sql; /* * Map storing mappings between column names and column types. * * Types used are from java.sql.Types. Would be nice if there were enumerated but they are integers. */ /* * #%L * interaction-jdbc-producer * %% * Copyright (C) 2012 - 2013 Temenos Holdings N.V. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import javax.ws.rs.core.Response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.support.JdbcUtils; import com.temenos.interaction.jdbc.exceptions.JdbcException; import com.temenos.interaction.jdbc.producer.JdbcProducer; public class ColumnTypesMap { private static final Logger LOGGER = LoggerFactory.getLogger(ColumnTypesMap.class); // Default primary key private static String DEFAULT_PRIMARY_KEY = "RECID"; // Somewhere to cache mapping information. // // TODO With the new oDataParser this member is almost unused. The column // data types can be determined from the // parsed oData4j data types. If these are wrong then the operation will // fail when the SQL query is executed. // This is retained only to check if the, illegal, INNER_RN_NAME column name // is present. private Map<String, Integer> typesMap; // Somewhere to store primary key. private String primaryKeyName; public ColumnTypesMap(JdbcProducer producer, String tableName, boolean primaryKeyNameRequired) throws SQLException, JdbcException { // This will open a new connection. Remember to close it latter. DataSource ds = producer.getDataSource(); Connection conn = ds.getConnection(); // Get the metadata DatabaseMetaData dsMetaData = conn.getMetaData(); // Read the initial map. this.typesMap = readColumnTypes(dsMetaData, tableName); // If required obtain the primary key. if (primaryKeyNameRequired) { try { this.primaryKeyName = readPrimaryKey(dsMetaData, tableName); } catch (SQLException sqlException) { // Re-throw throw sqlException; } catch (JdbcException jdbcException) { // Re-throw throw jdbcException; } finally { // Remember to close the connection conn.close(); } } // Close the connection conn.close(); } /* * USE THIS CONSTRUCTOR ONLY FOR UNIT TESTING. */ public ColumnTypesMap(Map<String, Integer> typesMap, String primaryKeyName) { this.typesMap = typesMap; this.primaryKeyName = primaryKeyName; } /* * Get type of a given column */ public Integer getType(String columnName) { return get(columnName); } /* * Get type and handle missing columns. */ Integer get(String columnName) { Integer type = typesMap.get(columnName); if (null == type) { throw new SecurityException("Jdbc column \"" + columnName + "\" does not exist."); } return type; } /* * Determines if a given column is numeric. */ public boolean isNumeric(String columnName) { Integer type = get(columnName); return JdbcUtils.isNumeric(type); } /* * Get the primary key name. */ public String getPrimaryKeyName() { return primaryKeyName; } /* * Utility to read primary key for a given table. */ String readPrimaryKey(DatabaseMetaData dsMetaData, String tableName) throws SQLException, JdbcException { String key = null; ResultSet result = dsMetaData.getPrimaryKeys(null, null, tableName); // Move cursor to first line. if (result.next()) { key = result.getString("COLUMN_NAME"); if (result.next()) { throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Table \"" + tableName + "\" has multiple primary keys. Not currently supported."); } } // If the primary key could not be found, and default column is present, // use that. if (null == key) { if (typesMap.containsKey(DEFAULT_PRIMARY_KEY)) { key = DEFAULT_PRIMARY_KEY; } else { throw new JdbcException( Status.INTERNAL_SERVER_ERROR, "Table \"" + tableName + "\" does not have a primary key or " + DEFAULT_PRIMARY_KEY + " column. For this table try multiple version of the command with \"?$filter=<Key name> eq <key_value>\"."); } } return key; } /* * Utility to read column types for a given table. */ public Map<String, Integer> readColumnTypes(DatabaseMetaData dsMetaData, String tableName) throws SQLException { // Create type map typesMap = new HashMap<String, Integer>(); ResultSet resultSet = dsMetaData.getColumns(null, null, tableName, null); int columnCount = 0; while (resultSet.next()) { typesMap.put(resultSet.getString("COLUMN_NAME"), resultSet.getInt("DATA_TYPE")); columnCount++; } if (0 == columnCount) { // Maybe there aren't any columns. So not necessarily an error. // However not normally expected so warn. LOGGER.warn("No column types found for. " + tableName + " Maybe not supported on this database."); } return typesMap; } }